【问题标题】:(Wide)String - storing in TFileStream, Delphi 7. What is the fastest way?(Wide)String - 存储在TFileStream,Delphi 7中。最快的方法是什么?
【发布时间】:2010-11-24 03:44:37
【问题描述】:

我正在使用 Delphi7(非 unicode VCL),我需要在 TFileStream 中存储大量 WideString。我不能使用 TStringStream 因为(宽)字符串与二进制数据混合,该格式预计可以加速加载和写入数据......但是我相信我当前加载/写入字符串的方式可能是我的代码的瓶颈......

目前我正在写一个字符串的长度,然后逐个字符地写它... 加载时,首先我加载长度,然后加载 char by char ...

那么,将 WideString 保存和加载到 TFileStream 的最快方法是什么?

提前致谢

【问题讨论】:

  • 更改代码的特定区域,因为您相信它可能是瓶颈,可能会浪费大量时间。你应该先测量,那里有很多工具可以帮助你,有些是免费的,有些是商业的。先试试这些链接:stackoverflow.com/questions/291631/…stackoverflow.com/questions/368938/delphi-profiling-tools
  • 谢谢,但我使用 QueryPerformanceCounter 来检测它;)无论如何,这肯定是瓶颈,因为逐个字符读取字符非常慢......所有其他操作只是保存一些简短的二进制数据.
  • 啊,好的。我只是对你使用“相信”和“可能”这两个词的反应,那么抱歉讲道;-)
  • 不客气,可能是我用错了那些词,我的英语很差;)

标签: delphi widestring tfilestream


【解决方案1】:

与其一次读取和写入一个字符,不如一次读取和写入它们:

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;

现在,从技术上讲,由于WideString 是Windows BSTR,它可以包含奇数 个字节。 Length 函数读取字节数并除以 2,因此上面的代码有可能(尽管不太可能)会截断最后一个字节。您可以改用此代码:

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;

Mghie's answer 的启发,已将我的ReadWrite 调用替换为ReadBufferWriteBuffer。如果后者无法读取或写入请求的字节数,则会引发异常。

【讨论】:

  • 您的第二个 WriteWideString() 版本无法编译(缺少对 PWideChar 的类型转换,缺少括号),但更重要的是它对于空字符串会失败。您的第二个 ReadWideString() 也应该检查长度 0 并在这种情况下简单地返回一个空字符串。
  • 我看不出它不适用于空字符串的原因; SysStringByteLen 为空指针返回零。类型转换为PWideChar 的要求是因为SysStringByteLen 被错误地声明为PWideChar 而不是WideString,或者BSTR 被错误地声明为PWideChar 而不是WideString。尽管如此,我已经解决了这个问题并解决了您的其他问题。谢谢。
  • 确实看到了一个原因,它可能会因为只有一个字节的字符串而失败。启用范围检查后,表达式ws[1] 在这种情况下应该会失败。 (Delphi QC 错误 9425 和 Free Pascal 错误 0010013 会影响它是否在任何特定版本上失败。)
  • 我在 Delphi 4 和 Delphi 2009 中使用空字符串尝试了您的代码,并且都返回了负(错误代码)值。这是在 Windows XP 64 上。
  • 好收获。 SysStringByteLen 确实被声明为采用PWideChar,并且类型转换为它提供了一个指向具有虚假长度的空宽字符串的指针 - 它应该是 0 但不是。这是我在测试中看到的负值。使用指针转换按预期工作,我进行了相应的编辑。感谢您的评论。
【解决方案2】:

宽字符串没有什么特别之处,要尽可能快地读写它们,你需要尽可能多地一次读写:

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;

【讨论】:

    【解决方案3】:

    WideStrings 包含 WideChar 的“字符串”,每个使用 2 个字节。如果你想将 UTF-16(WideStrings 内部使用)字符串存储在一个文件中,并且能够在记事本等其他程序中使用这个文件,你需要先写一个byte order mark#$FEFF

    如果你知道这一点,写作可以是这样的:

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

    阅读可以是这样的:

    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);
    

    【讨论】:

    • 他说他想要存储大量的字符串,它们会与二进制数据混合在一起,并且会以长度为前缀。绝对不是与记事本一起使用的东西。您的代码将整个流专用于单个字符串。
    • 代码无条件访问空字符串的第一个元素会导致访问冲突。
    【解决方案4】:

    您也可以使用 TFastFileStream 读取数据或字符串,我将单元粘贴在 http://pastebin.com/m6ecdc8c2 和下面的示例:

    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.
    

    【讨论】:

    • 注意:如果要读取的字符串包含任何空字符,则该代码将不起作用。
    • 代码无条件访问空字符串的第一个元素会导致访问冲突。
    猜你喜欢
    • 2010-09-22
    • 1970-01-01
    • 1970-01-01
    • 2016-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-17
    • 1970-01-01
    相关资源
    最近更新 更多