在another question中,David Heffernan发表了关于他“所有时间最不喜欢的德尔福构造”的评论:
SetLength(FItems, Length(FItems)+1);
他不喜欢的构造在Pebongo项目中大量使用,例如在this excerpt中:
procedure TBSONDocument.ReadStream( F: TStream );
var
len : integer;
elmtype : byte;
elmname : string;
begin
Clear;
f.Read( len, sizeof( len ) );
f.Read( elmtype, sizeof( byte ) );
while elmtype <> BSON_EOF do // Loop
begin
elmname := _ReadString( f );
SetLength( FItems, length( FItems ) + 1 ); // This, spotted by TOndrej
case elmtype of
BSON_ARRAY: FItems[high( FItems )] := TBSONArrayItem.Create;
BSON_BINARY: FItems[high( FItems )] := TBSONBinaryItem.Create;
...
end;
f.Read( elmtype, sizeof( byte ) );
end;
end;
有哪些替代方案?
答案 0 :(得分:9)
SetLength()
本身并不是坏事,而是增加了循环中的长度。坏代码示例:
SetLength( Result.FItems, 0 );
for i := 0 to high( FItems ) do
begin
SetLength(Result.FItems, Length(Result.Fitems)+1);
Result.FItems[i] := FItems[i].Clone;
end;
在这种情况下,数组被重新排列并在每次迭代时重新分配内存。您发布的示例未显示SetLength()
答案 1 :(得分:7)
我所说的是一次增加动态数组1个元素:
SetLength(FItems, Length(FItems)+1);
在循环中,对于大型数组,这可能导致内存地址碎片。发生这种情况时,即使总可用地址空间很大,您也会发现自己无法分配大量连续的内存块。如果受到32位地址空间的限制,这可能是一个非常现实的问题。此外,性能也可能是一个问题。
有多种方法可以避免这个问题:
TBSONDocument.Clone
。TList
或TList<T>
容器。虽然这些使用动态数组作为其底层存储,但它们实现了基于容量的分配方法。当它们满了时,它们会分配大量额外的物品,以备将来添加。这再次非常有效。答案 2 :(得分:3)
另一种方法是使用TList
,TObjectList
或通用列表等列表。
实际上,您发布的部分(SetLength(Result.FItems, Length(FItems));
)没问题。我相信大卫指的是另一个部分:SetLength(FItems, Length(FItems) + 1);
它应该是一个动态数组,并且在循环中可能效率低下。
答案 3 :(得分:3)
预先知道项目数量时可以使用的替代方法:每隔X项增加数组的长度,最后将数组的长度调整为添加的项目数。
示例:
Count := 0;
try
while not Query.EOF do
begin
if Length(MyArray) = Count then
SetLength(MyArray, Length(MyArray) + 50); //increase length with 50 items
MyArray[Count] := Query.Fields(0).AsString;
Inc(Count);
Query.Next;
end;
finally
SetLength(MyArray, Count); //adjust length to number of items added
end;
答案 4 :(得分:0)
TList使用David引用的非连续内存样式。这是他答案的补充。
这是一个真实世界的比喻:
“停车场中的汽车”:对象列表与记录数组的类比
数组
停车场里有一排汽车已经装满了。这排汽车是一个阵列。实际的汽车存储在阵列中。内存中数组的大小是所有真实汽车组合的大小。
将汽车添加到数组
假设您要添加另一辆车,但行中没有空间。因此,您必须构建一个更大的新行并将所有车辆移动到新行中。每次要在行中添加一个时,都必须这样做。那是一个数组。
列表
如果将汽车写入一张纸上,将汽车列入清单怎么办?每个VIN指的是停车场的车。纸上的那份清单是TList。它只存储每辆车的参考。
将汽车添加到TList
现在,假设您要将汽车添加到列表中。那么,你可以把车停在任何地方。如果您的地段已满,请将其停放在阿拉斯加州。没关系。只需将新的VIN添加到列表中即可。如果你的纸张填满,再拿一张纸。但是,不要移动汽车。
TList很好,因为实际的列表项不存储在列表中。 它只是指向项目的指针。那些指针相对较小, 加上TList预先分配空间(在类比中,每张纸)。