德尔福:快速(呃)宽字符串连接
-
26-09-2019 - |
题
我有一个函数,其工作是转换 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
班级。它在内部用于构建他所谓的东西 DomString
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 自己的内部操作。
笔记
- 很好地捕捉到错误地将所有字符串强制放入当前代码页(
VarAsString
而不是VarAsWideString
) - 一些 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
我现在无法花时间给您确切的代码。
但我认为你能做的最快的事情是:
循环遍历所有字符串并计算它们的总长度,同时添加您需要的额外表格标签。
使用 SetString 分配一个适当长度的字符串。
再次循环所有字符串并使用 “移动”程序 将字符串复制到最终字符串中的正确位置。
关键是,由于不断分配和释放内存,许多字符串串联所需的时间越来越长。一次分配将是您最大的节省时间的方法。
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。