从TList删除(0)是昂贵的,因为所有后续项目都需要向下移动。如果我需要从更大的列表的开头删除大量项目,那么最快的方法是什么?
答案 0 :(得分:8)
从TList
开头删除大量元素是很昂贵的。虽然类名称可以欺骗,但TList
实际上是一个数组。在TList
中没有删除范围的工具 - 必须单独删除每个项目,然后向下移动列表的其余部分。对于大范围而言,这会引发大量的重新分配和完整列表移动。
如果你有一个更现代的Delphi,你可以使用通用列表类TList<T>
,并利用DeleteRange
方法。该文件包括这一重要说明:
这是O(ACount)操作。
在Delphi 2006中,您可以编写具有相同性能特征的内容,如下所示:
procedure DeleteRange(List: TList; AIndex, ACount: Integer);
var
i: Integer;
NewCount: Integer;
begin
NewCount := List.Count-ACount;
Assert(AIndex>=0);
Assert(ACount>=0);
Assert(NewCount>=0);
for i := AIndex to NewCount-1 do
List[i] := List[i+ACount]
List.Count := NewCount;
end;
答案 1 :(得分:4)
正如Marcelo已经说过的那样,你可以复制整个块,但不是逐个项目,你可以使用TList.List
作为参数,通过一次调用Move()来移动整个块。
请注意TList.List
在旧的Delphi版本中是PPointerList
(^TPointerList; TPointerList = array[0..MaxListSize - 1] of Pointer;
),在Delphi XE2中成为TPointerList
(TPointerList = array of Pointer;
),所以你应该使用正确的间接:
TList(aList).List^[index] // for older Delphi's
和
TList(aList).List[index] // for Delphi XE2
答案 2 :(得分:2)
这是你在XE2中的表现,因为TList是一个指针数组。
在Delphi 2006上实现类似,但由于我没有2006年,所以我无法编写代码。
// I have 1000000 items, and I want to delete the first 5000
// Copy the pointer array items up the array
CopyMemory(@myList.List[0],
@myList.List[5000],
SizeOf(Pointer) * (myList.Count - 5000));
// Reset the count. Delphi cooperates as long as we're shrinking
myList.Count := myList.Count - 5000;
// You now have tons of unused memory at the end, it's fine
// but if you want to clean house
myList.Capacity := myList.Count;
只要指针指向的所有内容都是在其他地方管理的内存,就没有泄漏。
以下是证据:
type
TMyObject = class
index: Integer;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
myList: TList;
i: Integer;
myObject: TMyObject;
begin
// Create a list with 1000000 entries
myList := TList.Create;
for i := 1 to 1000000 do
begin
myObject := TMyObject.Create;
myObject.index := i;
myList.Add(myObject);
end;
// Delete the first 5000
CopyMemory(@myList.List[0],
@myList.List[5000],
SizeOf(Pointer) * (myList.Count - 5000));
myList.Count := myList.Count - 5000;
myList.Capacity := myList.Count;
// Show the new count
ShowMessage(IntToStr(myList.Count));
// Shows that my array now has objects 5001 and up
for i := 0 to 5 do
begin
myObject := TMyObject(myList.Items[i]);
ShowMessage(IntToStr(myObject.index));
end;
end;
证明有重大内存泄漏,但我们创建的对象只是为了显示值。
答案 3 :(得分:1)
如果订单很重要,并且您需要在前面删除N个项目:
for I := 0 to List.Count - N - 1 do
list[I] := list[I + N];
for I := list.Count - 1 downto list.Count - N do
list.Delete(I)
我没有仔细考虑过这段代码,所以你需要检查一下错误之类的内容。
如果订单无关紧要,您可以简单地将最后N个项目与前N个项目交换,并删除最后N个项目,如上所述。
答案 4 :(得分:1)
这是一个想法:如果您知道列表中的所有项目都已分配,您可以将要删除的项目取消,只需调用TList.Pack(),它会找出空白点的位置并将其他所有内容移到一边尽可能高效。这需要扫描所有项目,因此它可能不是您想要的,但它也不使用Delete(因此也可以防止Nitofy调用)。 Pack的实现在D2006和XE2之间没有什么变化,所以你可以依赖它的效率。
请注意,要删除要移除的项目,您可以使用List[aIndex] := nil
,但这仍会强制执行Notify()调用,因此List.List[aIndex] := nil
可能会更快。{/ p>
答案 5 :(得分:0)
首先,使用BeginUpdate和EndUpdate来阻止通过删除每个项目来更新TList的UI。
第二:尝试先删除列表中最低位的项目。换句话说,从列表中自上而下删除项目对其他项目的效率较低。