Delphi:快速(呃)宽字符串连接

时间:2010-06-09 15:15:33

标签: delphi performance delphi-5 widestring

我有一个职能是将ADO Recordset转换为html:

class function RecordsetToHtml(const rs: _Recordset): WideString;

该函数的内容涉及大量的字符串连接:

   while not rs.EOF do
   begin
      Result := Result+CRLF+
         '<TR>';

      for i := 0 to rs.Fields.Count-1 do
         Result := Result+'<TD>'+VarAsWideString(rs.Fields[i].Value)+'</TD>';

      Result := Result+'</TR>';
      rs.MoveNext;
    end;

有几千个结果,该功能需要用户感觉太长,无法运行。 Delphi Sampling Profiler表示 99.3%的时间用于宽字符串连接(@WStrCatN@WstrCat)。

有人能想出一种改进宽字符串连接的方法吗?我不认为Delphi 5有任何类型的字符串构建器。 Format不支持Unicode。


并确保没有人试图狡猾:假装你正在实施界面:

IRecordsetToHtml = interface(IUnknown)
    function RecordsetToHtml(const rs: _Recordset): WideString;
end;

更新一个

我想过使用IXMLDOMDocument,将HTML构建为xml。但后来我意识到最终的HTML将是xhtml而不是html - 一个微妙但重要的区别。

更新两个

Microsoft知识库文章:How To Improve String Concatenation Performance

5 个答案:

答案 0 :(得分:2)

WideString本质上很慢,因为它们是为兼容COM而实现的,并通过COM调用。如果您查看代码,它将继续重新分配字符串并调用SysAllocStringLen()&amp; C是来自oleaut32.dll的API。它不使用Delphi内存管理器,而是AFAIK它使用COM内存管理器。 由于大多数HTML页面不使用UTF-16,因此使用本机Delphi字符串类型和字符串列表可能会获得更好的结果,尽管您应该注意从UTF和实际代码页的转换,并且转换也会降低性能。 此外,您正在使用VarAsString()函数,该函数可能会将变体转换为 AnsiString ,然后转换为WideString。检查您的Delphi版本是否具有VarAsWideString()或类似功能以避免它,或者如果您确定您的变体永远不会为NULL,则依赖于Delphi自动转换。

答案 1 :(得分:1)

是的,你的算法显然是O(n ^ 2)。

不是返回string,而是返回TStringList,然后用

替换你的循环
   while not rs.EOF do
   begin
      Result.Add('<TR>');

      for i := 0 to rs.Fields.Count-1 do
         Result.Add( '<TD>'+VarAsString(rs.Fields[i].Value)+'</TD>' );

      Result := Result.Add('</TR>');
      rs.MoveNext;
    end;

然后,您可以使用Result

保存TStringList.SaveToFile

答案 2 :(得分:1)

我找到了最好的解决方案。 Delphi的开源HtmlParser有一个帮助TStringBuilder类。它在内部用于构建他称之为DomString的内容,它实际上是WideString的别名:

TDomString = WideString;

稍微摆弄他的班级:

TStringBuilder = class
public
   constructor Create(ACapacity: Integer);
   function EndWithWhiteSpace: Boolean;
   function TailMatch(const Tail: WideString): Boolean;
   function ToString: WideString;
   procedure AppendText(const TextStr: WideString);
   procedure Append(const value: WideString);
   procedure AppendLine(const value: WideString);
   property Length: Integer read FLength;
end;

例程的内容变为:

while not rs.EOF do
begin
   sb.Append('<TR>');

   for i := 0 to rs.Fields.Count-1 do
      sb.Append('<TD>'+VarAsWideString(rs.Fields[i].Value));

   sb.AppendLine('</TR>');

   rs.MoveNext;
end;

代码然后感觉无限地运行。分析显示很多改进; WideString操纵和长度计算变得可以忽略不计。取而代之的是FastMM自己的内部运营。

备注

  1. 错误地将所有字符串强制转换为当前代码页(VarAsString而不是VarAsWideString
  2. 某些HTML结束标记是可选的;遗漏的那些在逻辑上毫无意义。

答案 3 :(得分:1)

我现在无法花时间为您提供确切的代码。

但我认为你能做的最快的事情是:

  1. 遍历所有字符串并总计它们的长度,同时添加您需要的额外表格标签。

  2. 使用SetString分配一个适当长度的字符串。

  3. 再次遍历所有字符串并使用the "Move" procedure将字符串复制到最终字符串中的正确位置。

  4. 关键是,由于内存的不断分配和释放,对字符串的许多连接需要更长更长的时间。单个分配将是您最大的节省时间。

答案 4 :(得分:0)

Widestring不是引用计数,任何修改都意味着字符串操作。 如果您的内容不是unicode编码,您可以在内部使用本机字符串(引用计数)来连接字符串,然后将其转换为Widestring。示例如下:

var
  NativeString: string;
begin
   // ...
   NativeString := '';

   while not rs.EOF do
   begin
     NativeString := NativeString + CRLF + '<TR>';

     for i := 0 to rs.Fields.Count-1 do
       NativeString := NativeString + '<TD>'+VarAsString(rs.Fields[i].Value) + '</TD>';

     NativeString := NativeString + '</TR>';
     rs.MoveNext;
   end;

   Result := WideString(NativeString);

我还看到了另一种方法:将Unicode编码为UTF8String(作为引用计数),将它们连接起来,最后将UTF8String转换为Widestring。但我不确定,如果两个UTF8String可以直接连接。还应考虑编码时间。

无论如何,虽然Widestring连接比本机字符串操作慢得多。但IMO仍然可以接受。应该避免过多调整这类事情。认真考虑性能,然后你应该将Delphi升级到至少2009年。购买工具的成本是长期的,比做一个沉重的黑客更便宜。老特尔斐。