(Wide)String - storing in TFileStream, Delphi 7. What is the fastest way?

前端 未结 4 1041
[愿得一人]
[愿得一人] 2021-01-02 23:28

I\'m using Delphi7 (non-unicode VCL), I need to store lots of WideStrings inside a TFileStream. I can\'t use TStringStream as the (wide)strings are mixed with binary data, t

相关标签:
4条回答
  • WideStrings contain a 'string' of WideChar's, which use 2 bytes each. If you want to store the UTF-16 (which WideStrings use internally) strings in a file, and be able to use this file in other programs like notepad, you need to write a byte order mark first: #$FEFF.

    If you know this, writing can look like this:

    Stream1.Write(WideString1[1],Length(WideString)*2); //2=SizeOf(WideChar)
    

    reading can look like this:

    Stream1.Read(WideChar1,2);//assert returned 2 and WideChar1=#$FEFF
    SetLength(WideString1,(Stream1.Size div 2)-1);
    Stream1.Read(WideString1[1],(Stream1.Size div 2)-1);
    
    0 讨论(0)
  • 2021-01-02 23:52

    Rather than read and write one character at a time, read and write them all at once:

    procedure WriteWideString(const ws: WideString; stream: TStream);
    var
      nChars: LongInt;
    begin
      nChars := Length(ws);
      stream.WriteBuffer(nChars, SizeOf(nChars);
      if nChars > 0 then
        stream.WriteBuffer(ws[1], nChars * SizeOf(ws[1]));
    end;
    
    function ReadWideString(stream: TStream): WideString;
    var
      nChars: LongInt;
    begin
      stream.ReadBuffer(nChars, SizeOf(nChars));
      SetLength(Result, nChars);
      if nChars > 0 then
        stream.ReadBuffer(Result[1], nChars * SizeOf(Result[1]));
    end;
    

    Now, technically, since WideString is a Windows BSTR, it can contain an odd number of bytes. The Length function reads the number of bytes and divides by two, so it's possible (although not likely) that the code above will cut off the last byte. You could use this code instead:

    procedure WriteWideString(const ws: WideString; stream: TStream);
    var
      nBytes: LongInt;
    begin
      nBytes := SysStringByteLen(Pointer(ws));
      stream.WriteBuffer(nBytes, SizeOf(nBytes));
      if nBytes > 0 then
        stream.WriteBuffer(Pointer(ws)^, nBytes);
    end;
    
    function ReadWideString(stream: TStream): WideString;
    var
      nBytes: LongInt;
      buffer: PAnsiChar;
    begin
      stream.ReadBuffer(nBytes, SizeOf(nBytes));
      if nBytes > 0 then begin
        GetMem(buffer, nBytes);
        try
          stream.ReadBuffer(buffer^, nBytes);
          Result := SysAllocStringByteLen(buffer, nBytes)
        finally
          FreeMem(buffer);
        end;
      end else
        Result := '';
    end;
    

    Inspired by Mghie's answer, have replaced my Read and Write calls with ReadBuffer and WriteBuffer. The latter will raise exceptions if they are unable to read or write the requested number of bytes.

    0 讨论(0)
  • 2021-01-02 23:52

    You can also use TFastFileStream for reading the data or strings, I pasted the unit at http://pastebin.com/m6ecdc8c2 and a sample below:

    program Project36;
    
    {$APPTYPE CONSOLE}
    
    uses
      SysUtils, Classes,
      FastStream in 'FastStream.pas';
    
    const
      WideNull: WideChar = #0;
    
    procedure WriteWideStringToStream(Stream: TFileStream; var Data: WideString);
    var
      len: Word;
    begin
      len := Length(Data);
      // Write WideString length
      Stream.Write(len, SizeOf(len));
      if (len > 0) then
      begin
        // Write WideString
        Stream.Write(Data[1], len * SizeOf(WideChar));
      end;
      // Write null termination
      Stream.Write(WideNull, SizeOf(WideNull));
    end;
    
    procedure CreateTestFile;
    var
      Stream: TFileStream;
      MyString: WideString;
    begin
      Stream := TFileStream.Create('test.bin', fmCreate);
      try
        MyString := 'Hello World!';
        WriteWideStringToStream(Stream, MyString);
    
        MyString := 'Speed is Delphi!';
        WriteWideStringToStream(Stream, MyString);
      finally
        Stream.Free;
      end;
    end;
    
    function ReadWideStringFromStream(Stream: TFastFileStream): WideString;
    var
      len: Word;
    begin
      // Read length of WideString
      Stream.Read(len, SizeOf(len));
      // Read WideString
      Result := PWideChar(Cardinal(Stream.Memory) + Stream.Position);
      // Update position and skip null termination
      Stream.Position := Stream.Position + (len * SizeOf(WideChar)) + SizeOf(WideNull);
    end;
    
    procedure ReadTestFile;
    var
      Stream: TFastFileStream;
    
      my_wide_string: WideString;
    begin
      Stream := TFastFileStream.Create('test.bin');
      try
        Stream.Position := 0;
        // Read WideString
        my_wide_string := ReadWideStringFromStream(Stream);
        WriteLn(my_wide_string);
        // Read another WideString
        my_wide_string := ReadWideStringFromStream(Stream);
        WriteLn(my_wide_string);
      finally
        Stream.Free;
      end;
    end;
    
    begin
      CreateTestFile;
      ReadTestFile;
      ReadLn;
    end.
    
    0 讨论(0)
  • 2021-01-03 00:14

    There is nothing special about wide strings, to read and write them as fast as possible you need to read and write as much as possible in one go:

    procedure TForm1.Button1Click(Sender: TObject);
    var
      Str: TStream;
      W, W2: WideString;
      L: integer;
    begin
      W := 'foo bar baz';
    
      Str := TFileStream.Create('test.bin', fmCreate);
      try
        // write WideString
        L := Length(W);
        Str.WriteBuffer(L, SizeOf(integer));
        if L > 0 then
          Str.WriteBuffer(W[1], L * SizeOf(WideChar));
    
        Str.Seek(0, soFromBeginning);
        // read back WideString
        Str.ReadBuffer(L, SizeOf(integer));
        if L > 0 then begin
          SetLength(W2, L);
          Str.ReadBuffer(W2[1], L * SizeOf(WideChar));
        end else
          W2 := '';
        Assert(W = W2);
      finally
        Str.Free;
      end;
    end;
    
    0 讨论(0)
提交回复
热议问题