我有一个函数,其工作是转换 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;

对于几千个结果,任何用户都会觉得该函数运行时间太长。这 德尔福采样分析器 表明 99.3% 的时间花在宽字符串连接上(@WStrCatN@WstrCat).

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


为了确保没有人试图逃避:假设您正在实现该接口:

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

更新一

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

更新二

微软知识库文章: 如何提高字符串连接性能

有帮助吗?

解决方案 4

我找到了最好的解决方案。开源 Html解析器 对于Delphi,有一个助手 TStringBuilder 班级。它在内部用于构建他所谓的东西 DomStrings,实际上是 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 结束标签是可选的;省略了那些逻辑上没有意义的内容。

其他提示

WideString的本质上是缓慢的,因为他们实施了COM兼容性和经过COM调用。如果你看一下代码,它会不断重新分配字符串并调用SysAllocStringLen()&C这是从的oleaut32.dll的API。它不使用Delphi的内存管理器,但据我所知它使用COM的内存管理器。 因为大多数HTML网页不使用UTF-16,您可以使用本机德尔福字符串类型和字符串列表,但你应该小心从UTF转换和实际的代码页得到更好的结果,并且转换将降级性能,以及。 你也正在使用VarAsString()函数可能转换的变体到的 AnsiString类型,然后转换为一个WideString的。检查你的Delphi的版本有VarAsWideString()什么都功能,以避免它,或者依靠德尔福自动转换,如果你能确保你的变种永远不会是NULL。

烨,你的算法显然是为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

我现在无法花时间给您确切的代码。

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

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

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

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

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

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到UTF8字符串(如参考计数),将它们连接起来并最终转换到UTF8字符串WideString的。但我不知道,如果两个UTF8字符串可以直接连接在一起。上编码的时间也应予以考虑。

<强>总之,虽然WideString的级联是比天然的字符串操作要慢得多。但是,IMO还是可以接受的。应避免对这类事情太多调整。认真考虑的性能,你应该再升级你的Delphi至少在2009年买了工具的成本是长期的价格低于上做一个沉重的黑客旧的Delphi。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top