在 Delphi 中将字符串拆分为固定长度部分的快速方法
Fast way to split a string into fixed-length parts in Delphi
我需要将字符串拆分为具有固定长度子字符串的 TStringList。
目前我使用:
procedure StrToStringList(ASource: string; AList: TStrings; AFixedLen: Integer);
begin
Assert(Assigned(AList));
while Length(ASource) > AFixedLen do
begin
AList.Add(LeftStr(ASource, AFixedLen));
Delete(ASource, 1, AFixedLen);
end;
AList.Add(ASource);
end;
这有效,但似乎很慢。有更好/更快的想法吗?
已编辑:结果分析:
速度提升非常可观。
这是我的(主观)分析的结果。
数据大小:290KB,固定长度:100:
- 原码:58毫秒
- 赫弗南:1 毫秒
- 三角肌:1 毫秒
数据大小:2805KB,固定长度:100:
- 原码:5803毫秒
- 赫弗南:5 毫秒
- 三角肌:4 毫秒
我认为修改输入字符串很浪费。像这样避免这种情况:
var
Remaining: Integer;
StartIndex: Integer;
begin
Remaining := Length(ASource);
StartIndex := 1;
while Remaining > 0 do
begin
AList.Add(Copy(ASource, StartIndex, AFixedLen));
inc(StartIndex, AFixedLen);
dec(Remaining, AFixedLen);
end;
end;
这将减少堆分配量和复制量。
但是,如果您观察到性能几乎没有提高,我也不会感到惊讶。为了执行任何认真的优化,我们可能需要查看一些示例输入数据。
不要在循环中使用 delete
。它会导致整个字符串移动。使用从 1 开始的基于 integer
的索引变量,并在使用 copy
提取子字符串后每次增加 AFixedLen
直到到达末尾。
您的代码中可能有一些立即明显的优化:
不修改源字符串,只提取需要的
子字符串。然后,您可以使输入字符串参数为常量,
允许编译器进一步优化对过程的调用
由于您处理的是固定长度和输入字符串
已知长度,则可以预先计算出所需的容量
字符串列表并避免在添加列表时重新分配内存
到.
以下是我的处理方式:
procedure StrToStringList(const aSource: String;
const aList: TStrings;
const aFixedLen: Integer);
var
idx: Integer;
srcLen: Integer;
begin
aList.Capacity := (Length(aSource) div aFixedLen) + 1;
idx := 1;
srcLen := Length(aSource);
while idx <= srcLen do
begin
aList.Add(Copy(aSource, idx, aFixedLen));
Inc(idx, aFixedLen);
end;
end;
此处的容量计算可能会导致容量过剩 1,其中输入字符串正好除以固定长度,但这是一个可以忽略不计的开销恕我直言,并且在目标是最佳性能的情况下是可以接受的(替代方案是条件分支以不同方式修改或计算容量,以满足可能是少数情况的情况)。
我需要将字符串拆分为具有固定长度子字符串的 TStringList。
目前我使用:
procedure StrToStringList(ASource: string; AList: TStrings; AFixedLen: Integer);
begin
Assert(Assigned(AList));
while Length(ASource) > AFixedLen do
begin
AList.Add(LeftStr(ASource, AFixedLen));
Delete(ASource, 1, AFixedLen);
end;
AList.Add(ASource);
end;
这有效,但似乎很慢。有更好/更快的想法吗?
已编辑:结果分析:
速度提升非常可观。 这是我的(主观)分析的结果。
数据大小:290KB,固定长度:100:
- 原码:58毫秒
- 赫弗南:1 毫秒
- 三角肌:1 毫秒
数据大小:2805KB,固定长度:100:
- 原码:5803毫秒
- 赫弗南:5 毫秒
- 三角肌:4 毫秒
我认为修改输入字符串很浪费。像这样避免这种情况:
var
Remaining: Integer;
StartIndex: Integer;
begin
Remaining := Length(ASource);
StartIndex := 1;
while Remaining > 0 do
begin
AList.Add(Copy(ASource, StartIndex, AFixedLen));
inc(StartIndex, AFixedLen);
dec(Remaining, AFixedLen);
end;
end;
这将减少堆分配量和复制量。
但是,如果您观察到性能几乎没有提高,我也不会感到惊讶。为了执行任何认真的优化,我们可能需要查看一些示例输入数据。
不要在循环中使用 delete
。它会导致整个字符串移动。使用从 1 开始的基于 integer
的索引变量,并在使用 copy
提取子字符串后每次增加 AFixedLen
直到到达末尾。
您的代码中可能有一些立即明显的优化:
不修改源字符串,只提取需要的 子字符串。然后,您可以使输入字符串参数为常量, 允许编译器进一步优化对过程的调用
由于您处理的是固定长度和输入字符串 已知长度,则可以预先计算出所需的容量 字符串列表并避免在添加列表时重新分配内存 到.
以下是我的处理方式:
procedure StrToStringList(const aSource: String;
const aList: TStrings;
const aFixedLen: Integer);
var
idx: Integer;
srcLen: Integer;
begin
aList.Capacity := (Length(aSource) div aFixedLen) + 1;
idx := 1;
srcLen := Length(aSource);
while idx <= srcLen do
begin
aList.Add(Copy(aSource, idx, aFixedLen));
Inc(idx, aFixedLen);
end;
end;
此处的容量计算可能会导致容量过剩 1,其中输入字符串正好除以固定长度,但这是一个可以忽略不计的开销恕我直言,并且在目标是最佳性能的情况下是可以接受的(替代方案是条件分支以不同方式修改或计算容量,以满足可能是少数情况的情况)。