2

我有一个功能是将 ADORecordset转换为 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 知识库文章:如何提高字符串连接性能

4

5 回答 5

2

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

于 2010-06-10T14:08:24.847 回答
1

是的,您的算法显然在 O(n^2) 中。

不要返回 a string,而是尝试返回 a 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

于 2010-06-10T12:20:01.507 回答
1

我找到了最好的解决方案。Delphi的开源HtmlParser有一个辅助TStringBuilder类。它在内部用于构建他所谓DomString的 s,实际上是 s 的别名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 结束标签是可选的;省略那些在逻辑上没有意义的。
于 2010-06-11T00:01:49.490 回答
1

我现在不能花时间给你确切的代码。

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

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

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

  3. 再次循环遍历所有字符串并使用“移动”过程将字符串复制到最终字符串中的正确位置。

关键是由于不断分配和释放内存,许多连接到字符串的时间越来越长。一次分配将是您最大的节省时间。

于 2010-06-11T02:13:14.153 回答
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 年。购买工具的成本长期低于对旧 Delphi 进行大量黑客攻击。

于 2010-06-10T13:39:31.327 回答