我何时以及为什么要使用TStringBuilder?

时间:2009-10-18 19:13:18

标签: delphi unicode delphi-2009 stringbuilder

一年前我将程序从Delphi 4转换为Delphi 2009,主要是为了跳转到Unicode,同时也是为了获得Delphi多年改进的好处。

因此,我的代码是所有遗留代码。它使用的短字符串现在已经方便地变成了长Unicode字符串,并且我已经将所有旧的ANSI函数更改为新的等效函数。

但是在Delphi 2009中,他们引入了TStringBuilder类,可能是在.NET的StringBuilder类之后建模的。

我的程序执行了很多字符串处理和操作,并且可以一次将数百兆字节的大字符串加载到内存中。

我不太了解Delphi的TStringBuilder实现,但我听说它的一些操作比使用默认字符串操作更快。

我的问题是,是否值得我完成这项工作并将我的标准字符串转换为使用TStringBuilder类。这样做我会得到什么和失去什么?


感谢您的回答并引导我得出结论,除非需要.NET兼容性,否则不要打扰。

Delphi 2009 String Performance, Jolyon Smith states上的博客上:

  

但它看起来好像TStringBuilder主要作为.NET兼容性工具,而不是为Win32应用程序的开发人员提供任何实际好处,可能除了希望或需要单一来源Win32 /的开发人员.NET代码库,不需要考虑字符串处理性能。

6 个答案:

答案 0 :(得分:13)

据我所知,TStringBuilder只是为了与.NET和Java的一些奇偶校验而引入,它似乎更像是一个勾选框类型的功能,而不是任何重大进步。

共识似乎是TStringBuilder在某些操作中更快但在其他操作中更慢。

你的程序在TStringBuilder比较之前/之后听起来像一个有趣的程序但除了作为学术练习之外我不会这样做。

答案 1 :(得分:11)

基本上,我使用这些习语来构建字符串。最重要的区别是:

对于复杂的构建模式,第一个使我的代码更清晰,第二个只有我添加行并且通常包含许多Format调用。

当格​​式模式很重要时,第三个使代码更清晰。

我只在表达式非常简单时使用最后一个。

前两个习语之间的一些差异:

  • TStringBuilderAppend有很多重载,如果你想添加像TStringList.Add这样的行可以
  • ,还有{{3}}(只有两次重载)
  • TStringBuilder使用超容量方案重新分配底层缓冲区,这意味着对于大缓冲区和频繁附加,它可以比TStringList
  • 快很多
  • 要获取TStringBuilder内容,您必须调用{{3}}方法,这会降低速度。

所以:速度不是选择你的字符串附加成语最重要的事情。可读代码是。

答案 2 :(得分:9)

我试图改进解析文本文件(1.5GB)的旧例程。例程相当愚蠢,它正在构建一个这样的字符串:Result:= Result+ buff[i];

所以我认为TStringBuilder会显着提高速度。事实证明,'dumb'代码实际上比使用TStringBuilder的'改进'版本快114%。

因此,从字符构建字符串不是可以通过TStringBuilder获得速度提升的地方。

我的StringBuilder(下面)比经典s:= s + chr 快184.82倍(是184 !!!!!!)。 (对4MB字符串进行实验)

经典s:= s + c
时间:8502毫秒

procedure TfrmTester.btnClassicClick(Sender: TObject);
VAR
   s: string;
   FileBody: string;
   c: Cardinal;
   i: Integer;
begin
 FileBody:= ReadFile(File4MB);
 c:= GetTickCount;

 for i:= 1 to Length(FileBody) DO
  s:= s+ FileBody[i];

 Log.Lines.Add('Time: '+ IntToStr(GetTickCount-c) + 'ms');     // 8502 ms
end;

Prebuffered
时间:
     BuffSize = 10000; // 10k缓冲区= 406ms      BuffSize = 100000; // 100k缓冲区= 140ms      BuffSize = 1000000; // 1M缓冲区= 46毫秒

代码:
    procedure TfrmTester.btnBufferedClick(Sender:TObject);     VAR        s:字符串;        FileBody:string;        c:红衣主教;        CurBuffLen,marker,i:整数;     开始      FileBody:= ReadFile(File4MB);      c:= GetTickCount;

 marker:= 1;
 CurBuffLen:= 0;
 for i:= 1 to Length(FileBody) DO
  begin
   if i > CurBuffLen then
    begin
     SetLength(s, CurBuffLen+ BuffSize);
     CurBuffLen:= Length(s)
    end;
   s[marker]:= FileBody[i];
   Inc(marker);
  end;

 SetLength(s, marker-1); { Cut down the prealocated buffer that we haven't used }

 Log.Lines.Add('Time: '+ IntToStr(GetTickCount-c) + 'ms');
 if s <> FileBody
 then Log.Lines.Add('FAILED!');
end;

预补充,如班级 时间:
    BuffSize = 10000; // 10k缓冲区= 437ms
    BuffSize = 100000; // 100k缓冲区= 187ms
    BuffSize = 1000000; // 1M缓冲区= 78毫秒

代码:
    procedure TfrmTester.btnBuffClassClick(Sender:TObject);     VAR        StringBuff:TCStringBuff;        s:字符串;        FileBody:string;        c:红衣主教;        我:整数;     开始      FileBody:= ReadFile(File4MB);      c:= GetTickCount;

 StringBuff:= TCStringBuff.Create(BuffSize);
 TRY
   for i:= 1 to Length(FileBody) DO
    StringBuff.AddChar(filebody[i]);
   s:= StringBuff.GetResult;
 FINALLY
  FreeAndNil(StringBuff);
 END;

 Log.Lines.Add('Time: '+ IntToStr(GetTickCount-c) + 'ms');
 if s <> FileBody
 then Log.Lines.Add('FAILED!');
end;

这是班级:

{ TCStringBuff }

constructor TCStringBuff.Create(aBuffSize: Integer= 10000);
begin
 BuffSize:= aBuffSize;
 marker:= 1;
 CurBuffLen:= 0;
 inp:= 1;
end;


function TCStringBuff.GetResult: string;
begin
 SetLength(s, marker-1);                    { Cut down the prealocated buffer that we haven't used }
 Result:= s;
 s:= '';         { Free memory }
end;


procedure TCStringBuff.AddChar(Ch: Char);
begin
 if inp > CurBuffLen then
  begin
   SetLength(s, CurBuffLen+ BuffSize);
   CurBuffLen:= Length(s)
  end;

 s[marker]:= Ch;
 Inc(marker);
 Inc(inp);
end;

结论:如果您有大(超过10K)字符串,请停止使用s:= s + c 。即使你有小字符串也可能是这样,但你经常这样做(例如,你有一个函数在一个小字符串上做一些字符串处理,但你经常调用它)。 _

PS:您可能还想看到这个:https://www.delphitools.info/2013/10/30/efficient-string-building-in-delphi/2/

答案 3 :(得分:8)

TStringBuilder 仅用于为应用程序提供源代码兼容机制,以便在 Delphi Delphi.NET 中执行字符串处理。你在Delphi中牺牲了一些速度,以便在 Delphi.NET

中获得一些潜在的显着优势 .NET 中的 StringBuilder 概念解决了该平台上字符串实现的性能问题,发出 Delphi (本机代码)平台根本没有。

如果您没有编写需要为本机代码和 Delphi.NET 编译的代码,那么根本没有理由使用 TStringBuilder

答案 4 :(得分:7)

根据Marco Cantu而不是速度,但您可能会获得更清晰的代码以及与.Net更好的代码兼容性。 Here(以及一些更正here)使用TStringBuilder的另一个速度测试速度不快。

答案 5 :(得分:6)

TStringBuilder基本上只是一个me-too功能,就像LachlanG所说的那样。它在.NET中是必需的,因为CLR字符串是不可变的,但是Delphi没有那个问题所以它并不需要字符串构建器作为解决方法。