我有一个职能是将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
答案 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自己的内部运营。
备注强>
VarAsString
而不是VarAsWideString
)答案 3 :(得分:1)
我现在无法花时间为您提供确切的代码。
但我认为你能做的最快的事情是:
遍历所有字符串并总计它们的长度,同时添加您需要的额外表格标签。
使用SetString分配一个适当长度的字符串。
再次遍历所有字符串并使用the "Move" procedure将字符串复制到最终字符串中的正确位置。
关键是,由于内存的不断分配和释放,对字符串的许多连接需要更长更长的时间。单个分配将是您最大的节省时间。
答案 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年。购买工具的成本是长期的,比做一个沉重的黑客更便宜。老特尔斐。