【问题标题】:Better way to resize a TStringList?调整 TStringList 大小的更好方法?
【发布时间】:2014-01-13 09:58:58
【问题描述】:

我经常发现我需要“调整”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&lt;String&gt;
  • 我不记得曾经调整字符串列表的大小以恰好容纳 N 个元素,所以我想知道您的用例吗?也许其他数据结构更适合您的需求。
  • 为什么需要调整列表大小?这是一个重要的问题,因为它对最佳答案有重大影响。例如。因为其他代码需要恰好 N 个元素而调整大小与在知道要保存多少个字符串的情况下调整大小以避免极大列表的冗余内存开销大不相同。
  • @CraigYoung Resizing 因为我有一个必须显示 X 项的可视组件(例如 TValueListEditor)。

标签: delphi tstringlist


【解决方案1】:

TStringList.Assign的实现来看,没有更好的办法做到这一点。他们基本上是调用Clear,然后将字符串一一添加。

您当然应该将代码放入实用程序方法中:

procedure ResizeStringList(List : TStrings; ANewSize: Integer);
begin
...
end;

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

【讨论】:

    【解决方案2】:

    你问题中的方法是你能做的最好的。如果你使用类助手,你可以让它更干净。例如:

    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);
    

    【讨论】:

    • TStringList.Capacity 属性怎么样?
    • @VilleKrumlinde 控制capacity而不是计数。
    • @DavidHeffernan 是的,但是在 Addloop 之前添加 if Value &gt; Count then Capacity = Value 可以提高性能。
    • @Roddy 怀疑它会产生任何明显的不同。字符串列表代码已经大量增加容量。看TStringList.Grow
    【解决方案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;
    

    更新Capacity后,如果有新分配的内存,使用FillChar初始化。这比一次添加/删除一项要高效得多。

    因此,您可以提供自己独立的 TStrings 子类的具体实现,或者简单地复制 Delphi 的 TStringList,其中包含适当的 SetCount 方法。

    尽管如此,我发现这部分代码不太可能会遇到任何性能问题,因此您自己的解决方案包含在适当的实用方法中就足够了。 David's answer 也不错,尽管我个人认为“类助手”功能没有那么有用。实现类助手的“旧方式”更加通用。

    【讨论】:

    • 我会说“不幸的缺点”是Count 不可写。虽然它们是相关的,但容量代表了与计数完全不同的概念。
    • @Roddy True。毕竟密切相关的TList 允许Count 是可写的。真的没有任何理由让一个可写而不是另一个。
    • @DavidHeffernan 你错了。 (也许您只是误解了我所指的内容。)Grow 所做的唯一一件事就是随着字符串数量的增加有效地扩展内部列表的内存分配。但是,它不会清除新分配的内存,这就是为什么需要在循环中添加空字符串的原因。每个新字符串还分配一个关联的对象引用(到 nil)。与 FillChar 在新分配的内存上相比,这非常低效。
    • Grow 是通过调用SetCapacity 来实现的。这是通过调用SetLength 来实现的。这反过来又通过一次调用 FillChar 来初始化新内存。
    【解决方案4】:
    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);
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-05-13
    • 1970-01-01
    • 2017-10-28
    • 1970-01-01
    • 1970-01-01
    • 2012-04-15
    • 2014-01-10
    • 2013-12-10
    相关资源
    最近更新 更多