2013-01-10 120 views
2

我們有一個庫函數是這樣的:爲什麼TStringStream在轉換爲字符串時不刪除BOM?

class function TFileUtils.ReadTextStream(const AStream: TStream): string; 
var 
    StringStream: TStringStream; 
begin 
    StringStream := TStringStream.Create('', TEncoding.Unicode); 
    try 
    // This is WRONG since CopyFrom might rewind the stream (see Remys comment) 
    StringStream.CopyFrom(AStream, AStream.Size - AStream.Position); 
    Result := StringStream.DataString; 
    finally 
    StringStream.Free; 
    end; 
end; 

當我檢查由該函數返回的第一個字符是(小端)BOM的字符串。

TStringStream爲什麼不忽略BOM?

有沒有更好的方法來做到這一點?我不需要向後兼容老的Delphi版本,XE2的工作解決方案將會很好。

+2

我沒有看到有什麼*錯*。 'TStream.CopyFrom'只是簡單地複製所有被告知的數據。 –

+0

@RobKennedy你說得對,但我希望DataString函數可以返回沒有BOM的字符串。我稍微改變了問題標題。 –

+1

僅供參考,如果第二個參數爲0,則CopyFrom()將流的Position重置爲0,並讀取整個流。如果Position位於流的末尾,那麼最終可能會返回您不期望的內容。 –

回答

8

BOM必須來自源TStream,因爲TStringStream不寫入BOM。如果您想忽略源代碼中存在的物料清單,則必須在複製數據之前手動執行此操作,例如:

class function TFileUtils.ReadTextStream(const AStream: TStream): string; 
var 
    StreamPos, StreamSize: Int64; 
    Buf: TBytes; 
    NumBytes: Integer; 
    Encoding: TEncoding; 
begin 
    Result := ''; 

    StreamPos := AStream.Position; 
    StreamSize := AStream.Size - StreamPos; 

    // Anything available to read? 
    if StreamSize < 1 then Exit; 

    // Read the first few bytes from the stream... 
    SetLength(Buf, 4); 
    NumBytes := AStream.Read(Buf[0], Length(Buf)); 
    if NumBytes < 1 then Exit; 
    Inc(StreamPos, NumBytes); 
    Dec(StreamSize, NumBytes); 

    // Detect the BOM. If you know for a fact what the TStream data is encoded as, you can assign the Encoding variable to the appropriate TEncoding object and GetBufferEncoding() will check for that encoding's BOM only... 
    SetLength(Buf, NumBytes); 
    Encoding := nil; 
    Dec(NumBytes, TEncoding.GetBufferEncoding(Buf, Encoding)); 

    // If any non-BOM bytes were read than rewind the stream back to that position... 
    if NumBytes > 0 then 
    begin 
    AStream.Seek(-NumBytes, soCurrent); 
    Dec(StreamPos, NumBytes); 
    Inc(StreamSize, NumBytes); 
    end else 
    begin 
    // Anything left to read after the BOM? 
    if StreamSize < 1 then Exit; 
    end; 

    // Now read and decode whatever is left in the stream... 
    StringStream := TStringStream.Create('', Encoding); 
    try 
    StringStream.CopyFrom(AStream, StreamSize); 
    Result := StringStream.DataString; 
    finally 
    StringStream.Free; 
    end; 
end; 
+0

我接受了這個答案,因爲它解釋了發生了什麼。 Hower我將使用'TStreamReader'解決方案,因爲它是最短的,顯然也是獲得我想要的最快速的方法。 –

2

顯然TStreamReader不會有同樣的問題:

var 
    StreamReader: TStreamReader; 
begin 
    StreamReader := TStreamReader.Create(AStream); 
    try 
    Result := StreamReader.ReadToEnd; 
    finally 
    StreamReader.Free; 
    end; 
end; 

TStringList也可以(感謝whosrdaddy):

var 
    Strings: TStringList; 
begin 
    Strings := TStringList.Create; 
    try 
    Strings.LoadFromStream(AStream); 
    Result := Strings.Text; 
    finally 
    Strings.Free; 
    end; 
end; 

我還測量了這兩種方法,並TStreamReader似乎是約兩倍快速。

+2

對於TStrings也是如此(http://docwiki.embarcadero.com/RADStudio/XE3/en/Using_TEncoding_for_Unicode_Files),也許你可以將它添加到你的答案中。 – whosrdaddy

+3

請注意,將流加載到字符串列表中並將列表重新加入單個字符串後,結果中的行結束可能與原始來源不同。 –

+3

TStreamReader的構造函數有一個可選的'DetectBOM'參數。你的例子是將該參數設置爲「True」,這就是爲什麼它沒有受到原始問題的影響。 –

相關問題