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

发布于 2024-12-19 08:34:54 字数 73 浏览 2 评论 0原文

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

Delete (0) from a TList is expensive because all the subsequent items need to be moved down. If I need to delete a large number of items from the start of an even larger list what's the fastest way?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

清醇 2024-12-26 08:34:54

TList 开头删除大量元素的成本很高。尽管类名具有欺骗性,但 TList 实际上是一个数组。在 TList 中,没有删除范围的功能 - 每个项目必须单独删除,然后列表的其余部分向下移动。对于较大的范围,这将引发大量的重新分配和完整的列表移动。

如果您有更现代的 Delphi,您可以使用通用列表类 TList并利用 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;

Deleting a large range of elements from the beginning of a TList is expensive. Although the class name flatters to deceive, a TList is in fact an array. In TList there is no facility to delete a range–each item must be deleted individually and then the rest of the list is moved down. For a large range that's going to provoke an awful lot of reallocations and full list moves.

If you had a more modern Delphi you could use the generic list class TList<T> and avail yourself of the DeleteRange method. The documentation includes this vital note:

This is an O(ACount) operation.

In Delphi 2006 you can write something with equivalent performance characteristics like this:

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;
反差帅 2024-12-26 08:34:54

正如 Marcelo 已经说过的,您可以复制整个块,但您可以使用 TList.List 作为参数,通过一次调用 Move() 来移动整个块,而不是逐项执行该操作。

请注意,TList.List 是一个 PPointerList (^TPointerList; TPointerList = array[0..MaxListSize - 1] of Pointer;)较旧的 Delphi 版本并在 Delphi XE2 中成为 TPointerList (TPointerList = array of Pointer;),因此您应该使用正确的间接寻址

TList(aList).List^[index] // for older Delphi's

TList(aList).List[index]  // for Delphi XE2

As Marcelo already said, you could copy down the whole block, but instead of doing that item by item, you could move the entire with one call to Move(), using TList.List as an argument.

Do note that TList.List was a PPointerList (^TPointerList; TPointerList = array[0..MaxListSize - 1] of Pointer;) in older Delphi versions and became a TPointerList (TPointerList = array of Pointer;) in Delphi XE2, so you should use the correct indirection:

TList(aList).List^[index] // for older Delphi's

and

TList(aList).List[index]  // for Delphi XE2
满天都是小星星 2024-12-26 08:34:54

由于 TList 是一个指针数组,因此在 XE2 中执行此操作的方法如下。

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;

该证明存在严重内存泄漏,但我们创建对象只是为了显示值。

Here's how you do it in XE2, since TList is an array of pointers.

The implementation will be similar on Delphi 2006, but I can't write the code since I don't have 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;

As long as all of the things the pointers point to are memory managed elsewhere, there's no leak.

Here's the proof:

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;

The proof has major memory leaks, but we created the objects just to show the values.

多情癖 2024-12-26 08:34:54

如果顺序很重要,并且您有 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 项。

If order matters, and you have N items to remove at the front:

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)

I haven't thought this code through very well, so you'll need to check for off-by-one errors and the like.

If order doesn't matter, you can simply exchange the last N items with the first N items, and the delete the last N items as above.

软甜啾 2024-12-26 08:34:54

这里有一个想法:如果您知道列表中的所有项目都已分配,则可以将要删除的项目置零,然后只需调用 TList.Pack(),它会找出空位在哪里,并尽可能有效地将其他所有项目移到一边。不过,这需要扫描所有项目,因此它可能不是您想要的,但它也不使用删除(从而阻止 Nitofy 调用)。 Pack 的实现在 D2006 和 XE2 之间没有发生任何变化,因此您可以依赖它的效率。

请注意,要删除要删除的项目,您可以使用 List[aIndex] := nil 但这仍会强制调用 Notify(),因此 List.List[aIndex] : = nil 可能会更快。

Here's a thought : If you know all items in your List are assigned, you could nil the ones you want to remove and just call TList.Pack(), which figures out where the empty spots are and moves everything else aside as efficiently as possible. This requires a scan through all items though, so it might not be what you want, but it doesn't use Delete (and thus prevents Nitofy calls) also. The implementation of Pack hasn't changed a bit between D2006 and XE2, so you can depend on it's efficiency.

Note, that to nil the items you want removed, you could use List[aIndex] := nil but that would still impose a Notify() call, so List.List[aIndex] := nil might be faster for that.

江湖正好 2024-12-26 08:34:54

首先,使用 BeginUpdate 和 EndUpdate 来防止通过删除每个项目来更新 TList 的 UI。

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

First of all, use BeginUpdate and EndUpdate to prevent updating the UI of the TList by deleting each item.

second: try to delete the items at the most lower one in the list first. In other word, deleting items from down to top the list takes less efficient on other items.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文