从Delphi中的TList开始删除大块项目的有效方法是什么?

时间:2011-12-01 22:12:19

标签: delphi delphi-2006 tlist

从TList删除(0)是昂贵的,因为所有后续项目都需要向下移动。如果我需要从更大的列表的开头删除大量项目,那么最快的方法是什么?

6 个答案:

答案 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中成为TPointerListTPointerList = 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。

第二:尝试先删除列表中最低位的项目。换句话说,从列表中自上而下删除项目对其他项目的效率较低。