Каков эффективный способ удаления большого блока элементов из начала TList в Delphi

удалить (0) из TList дорого, потому что все последующие элементы должны быть перемещены вниз. Если мне нужно удалить большое количество элементов из начала еще большего списка, какой самый быстрый способ?

6 ответов


удаление большого диапазона элементов с начала TList дорого. Хотя название класса льстит обманывать, a TList фактически является массивом. В TList нет возможности удалить диапазон–каждый элемент должен быть удален отдельно, а затем остальная часть списка перемещается вниз. Для большого диапазона, который будет провоцировать очень много перераспределений и полных движений списка.

если бы у вас был более современный Delphi, вы могли бы использовать общий класс list 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;

как уже сказал Марсело, вы можете скопировать весь блок, но вместо того, чтобы делать это пункт за пунктом, вы можете переместить весь с одним вызовом Move (), используя TList.List в качестве аргумента.

отметим, что TList.List был PPointerList (^TPointerList; TPointerList = array[0..MaxListSize - 1] of Pointer;) в старых версиях Delphi и стал TPointerList (TPointerList = array of Pointer;) в Delphi XE2, поэтому вы должны использовать правильную косвенность:

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

и

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

вот как вы это делаете в 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;

доказательство имеет основные утечки памяти, но мы создали объекты только для отображения значений.


Если порядок имеет значение, и у вас есть 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)

Я не очень хорошо продумал этот код, поэтому вам нужно будет проверить наличие ошибок off-by-one и тому подобное.

если заказ не имеет значения, вы можете просто обменять последние N элементов с первыми n элементов и удалить последние N элементов, как указано выше.


вот мысль: Если вы знаете, что все элементы в вашем списке назначены, вы можете свести к нулю те, которые хотите удалить, и просто вызвать TList.Pack (), который выясняет, где находятся пустые места,и максимально эффективно перемещает все остальное. Это требует сканирования всех элементов, поэтому это может быть не то, что вы хотите, но он не использует Delete (и, таким образом, предотвращает вызовы Nitofy). Реализация пакета немного не изменилась между D2006 и XE2, поэтому вы можете зависеть от его эффективность.

обратите внимание, что для удаления элементов, которые вы хотите удалить, вы можете использовать List[aIndex] := nil но это все равно наложит вызов Notify (), поэтому List.List[aIndex] := nil может быть быстрее для этого.


прежде всего, используйте BeginUpdate и EndUpdate, чтобы предотвратить обновление пользовательского интерфейса TList путем удаления каждого элемента.

second: сначала попробуйте удалить элементы в самом нижнем из списка. Другими словами, удаление элементов из списка сверху вниз менее эффективно для других элементов.