更好的TStringList调整大小的方法?

8
我经常需要“调整大小”TStringList以确保容纳N个元素,无论是添加额外的空字符串到列表中还是删除不必要的字符串。 在C++ STL容器上,我可以使用resize 方法,但似乎不存在。因此,我通常会做类似以下的操作(警告:伪代码!)。
list.beginUpdate;

while list.Count < requiredSize do
begin
   list.add('');
end;

while list.Count > requiredSize do
begin
   list.delete(list.count-1);
end;

list.endUpdate;

有没有更简单的方法做这件事,我有没有忽略什么?

为什么不使用 TList<String> 呢? - Arioch 'The
我不记得曾经需要调整字符串列表的大小以恰好容纳N个元素,所以我想知道你的用例是什么?也许其他数据结构更适合您的需求。 - Lieven Keersmaekers
为什么需要调整列表的大小?这是一个重要的问题,因为它对最佳答案有着显著影响。例如,当其他代码期望恰好N个元素时进行调整大小与在你知道想要保存多少个字符串时调整大小以避免极大列表的冗余内存开销是完全不同的。 - Disillusioned
@CraigYoung 调整大小,因为我有一个必须显示 X 个项目的可视化组件(例如 TValueListEditor)。 - Roddy
4个回答

7
TStringList.Assign的实现来看,没有更好的方法来完成这个任务。基本上,它们会调用Clear并逐个添加字符串。
当然,你应该将你的代码放入一个实用方法中:
procedure ResizeStringList(List : TStrings; ANewSize: Integer);
begin
...
end;

或者您可以使用类辅助器,使您的方法看起来像是TStringList本身的一部分。

3

你所提出的方法是最好的。如果你使用类辅助工具,可以使代码更简洁。例如:

type
  TStringsHelper = class helper for TStrings
    procedure SetCount(Value: Integer);
  end;

procedure TStringsHelper.SetCount(Value: Integer);
begin
  BeginUpdate;
  try
    while Count<Value do
      Add('');
    while Count>Value do
      Delete(Count-1);
  finally
    EndUpdate;
  end;
end;

然后你可以写:

List.SetCount(requiredSize);

2
TStringList.Capacity属性怎么样? - Ville Krumlinde
@VilleKrumlinde 这控制的是容量,而不是计数。 - David Heffernan
1
@DavidHeffernan 是的,但在Add循环之前添加一个if Value > Count then Capacity = Value可以提高性能。 - Roddy
@Roddy 不确定这会有任何显著的差别。字符串列表代码已经以大块增加容量。看看 TStringList.Grow - David Heffernan

3
Capacity 属性几乎是理想的,因为它将在内部数组中分配正确数量的条目。然而,它有不幸的缺点:
  • 新分配的内存未初始化。
  • 元素数 Strings.Count 未更新。
由于 Delphi 组件架构引用了基本类型 TStrings,您可以提供自己的具体子类,以支持更有效的调整大小功能。例如,请考虑以下 TList.SetCount 实现。
procedure TList.SetCount(NewCount: Integer);
var
  I: Integer;
begin
  if (NewCount < 0) or (NewCount > MaxListSize) then
    Error(@SListCountError, NewCount);
  if NewCount > FCapacity then
    SetCapacity(NewCount);
  if NewCount > FCount then
    FillChar(FList^[FCount], (NewCount - FCount) * SizeOf(Pointer), 0)
  else
    for I := FCount - 1 downto NewCount do
      Delete(I);
  FCount := NewCount;
end;

更新容量后,如果有新分配的内存,则使用FillChar进行初始化。这比逐个添加/删除项目要高效得多。
因此,您可以提供自己独立的TStrings子类的具体实现,或者只需复制Delphi的TStringList,其中包括一个适当的SetCount方法。
然而,我认为这段代码不太可能出现任何性能问题,因此您自己的解决方案包装在适当的实用程序方法中就足够了。David的答案也很好,但我个人认为“类助手”功能并不是那么有用。实现类助手的“旧方法”更加灵活。

1
我会说这个“不幸的缺点”是Count不可写。虽然它们相关,但容量代表了一个完全不同的概念,与计数不同。 - Roddy
@Roddy 确实。毕竟,与之密切相关的 TList 允许 Count 可写。没有任何理由使一个可写而另一个不可写。 - Disillusioned
@DavidHeffernan 你对此有误。(也许你只是误解了我的意思。)Grow 唯一的作用就是在字符串数量增加时高效地扩展内部列表的内存分配。然而,它并不会清除新分配的内存,这就是为什么需要在循环中添加空字符串的原因。每个新字符串还会分配一个关联的对象引用(指向 nil)。与 FillChar 相比,这种方法非常低效。 - Disillusioned
Grow 是通过调用 SetCapacity 实现的。而 SetCapacity 又是通过调用 SetLength 实现的。而 SetLength 则通过单次调用 FillChar 来初始化新内存。 - David Heffernan

0
var
    List:  TStringList;

Assert(requiredSize >= 0);
if requiredSize > List.Count then
    List.Capacity := requiredSize
else
    while List.Count > requiredSize do
        List.Delete(List.Count - 1);

1
根据我的回答(https://dev59.com/Bnvaa4cB1Zd3GeqPAjzo#21091387),设置容量存在两个问题。 - Disillusioned

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接