Skip to content

可变类型的 List

小马哥 edited this page Dec 18, 2016 · 51 revisions

简介

尽管 Delphi 的 VCL/CLX 和 Free Pascal 的 FCL 本身提供了一些 List 类型(例如:TList,TInterfaceList,TStringList 等),但这些 List 类型在处理混合类型数据时并不方便。

比如 TList 中的元素都是无类型指针,虽然指针可以指向任何类型,但是却无法在同一个 TList 对象中同时存放指向几种不同类型数据的指针,因为这样原本的数据类型在 TList 对象中丢失了,当取出元素指针时您无法获知这个指针指向的究竟是何种类型的数据。

而 TInterfaceList 的元素都是接口,无法存放接口以外的类型。

TStringList 既可以作为一个纯粹存放字符串元素的 List,也可以作为一个通过字符串来存取对象的 Map,不过它不是通过 Hash 方式查找的,所以效率不高,操作方式也不同于一般的 Map 类型,不是很方便。而且,用它也无法存取字符串和对象以外的类型。

Delphi 2009 以后引入的泛型容器基本上解决了上述问题,但是对于低版本的 Delphi 和 FreePascal 来说,却无法使用。

为了解决这个问题,Hprose 提供了一套可变类型的 List。

这套可变类型的 List 是基于接口来实现的,因此具有引用计数的自动内存管理功能。

继承关系图

 +----------------+                                                        
 | IReadWriteSync |                                                        
 +----------------+                                                        
          |                                                                
          v                                                                
 +----------------+   +-------------------+        +-----------------+     
 | IImmutableList |   | TInterfacedObject |        | IListEnumerator |     
 +----------------+   +-------------------+        +-----------------+     
          |                     |                                          
          v                     v                                          
      +-------+         +---------------+          +----------------------+
      | IList |-------->| TAbstractList |<---------| IInvokeableVarObject |
      +-------+         +---------------+          +----------------------+
          |                     |                                          
          |                     |             +---------------------------+
+---------+---------------------+------------>| ICaseInsensitiveArrayList |
|         |                     |             +---------------------------+
|         |                     |                           |              
|         v                     v                           v              
|  +------------+        +------------+       +---------------------------+
+--| IArrayList |------->| TArrayList |------>| TCaseInsensitiveArrayList |
   +------------+        +------------+       +---------------------------+
          |                     |                                          
          |                     |            +----------------------------+
+---------+---------------------+----------->| ICaseInsensitiveHashedList |
|         |                     |            +----------------------------+
|         |                     |                           |              
|         v                     v                           v              
|  +-------------+       +-------------+     +----------------------------+
+--| IHashedList |------>| THashedList |---->| TCaseInsensitiveHashedList |
   +-------------+       +-------------+     +----------------------------+

IImmutableList 接口

IImmutableList 接口中提供了一些对列表只读操作的一些方法和属性,其定义如下:

IImmutableList = interface(IReadWriteSync)
  ['{1D116D24-E964-E019-FC27-AFE4BF2A181D}']
    function Get(Index: Integer): Variant;
    function GetCapacity: Integer;
    function GetCount: Integer;
    function Contains(const Value: Variant): Boolean;
    function GetEnumerator: IListEnumerator;
    function IndexOf(const Value: Variant): Integer; overload;
    function IndexOf(const Value: Variant; StartIndex: Integer): Integer; overload;
    function IndexOf(const Value: Variant; StartIndex, ACount: Integer): Integer; overload;
    function LastIndexOf(const Value: Variant): Integer; overload;
    function LastIndexOf(const Value: Variant; StartIndex: Integer): Integer; overload;
    function LastIndexOf(const Value: Variant; StartIndex, ACount: Integer): Integer; overload;
    function Join(const Glue: string = ',';
                  const LeftPad: string = '';
                  const RightPad: string = ''): string;
    procedure Lock;
    procedure Unlock;
    function ToArray: TVariants; overload;
    function ToArray(VarType: TVarType): Variant; overload;
    function First: Variant;
    function Last: Variant;
    property Item[Index: Integer]: Variant read Get; default;
    property Capacity: Integer read GetCapacity;
    property Count: Integer read GetCount;
  end;

它是 IList 的父接口。

你可能会奇怪 IImmutableList 作为一个不变列表接口,其实是不需要锁的,为何要继承 IReadWriteSync 接口呢?

原因是 Delphi 的接口本身只支持单继承。如果不在 IImmutableList 中继承 IReadWriteSync 接口的话,就没有办法在 IList 接口中同时继承 IImmutableListIReadWriteSync 这两个独立的接口。

IImmutableList 中还提供了枚举器操作:

    function GetEnumerator: IListEnumerator;

该操作返回一个 IListEnumerator 接口对象。IListEnumerator 接口定义如下:

  IListEnumerator = interface
  ['{767477EC-A143-4DC6-9962-A6837A7AEC01}']
    function GetCurrent: Variant;
    function MoveNext: Boolean;
    procedure Reset;
    property Current: Variant read GetCurrent;
  end;

有了该操作之后,在 Delphi 2005 以上或 FreePascal 2.5.1 以上的版本中,就可以通过 for...in 语句来操作 Hprose 中所有列表接口的对象了。

IList 接口

该接口继承自 IImmutableList 接口。

它在 IImmutableList 接口的基础上,增加了添加、删除、修改、排序、翻转等操作。它的定义如下:

  IList = interface(IImmutableList)
  ['{DE925411-42B8-4DB3-A00C-B585C087EC4C}']
    procedure Put(Index: Integer; const Value: Variant);
    procedure SetCapacity(NewCapacity: Integer);
    procedure SetCount(NewCount: Integer);
    function Add(const Value: Variant): Integer;
    procedure AddAll(const AList: IImmutableList); overload;
    procedure AddAll(const Container: Variant); overload;
    procedure AddAll(const ConstArray: array of const); overload;
    procedure Assign(const Source: IImmutableList);
    procedure Clear;
    function Compare(const Value1, Value2: Variant): Integer;
    function Delete(Index: Integer): Variant;
    procedure DeleteRange(Index, ACount: Integer);
    procedure Exchange(Index1, Index2: Integer);
    procedure Insert(Index: Integer; const Value: Variant);
    procedure InsertRange(Index: Integer; const AList: IImmutableList); overload;
    procedure InsertRange(Index: Integer; const Container: Variant); overload;
    procedure InsertRange(Index: Integer; const ConstArray: array of const); overload;
    procedure InitLock;
    procedure InitReadWriteLock;
    procedure Move(CurIndex, NewIndex: Integer);
    function Remove(const Value: Variant): Integer; overload;
    function Remove(const Value: Variant; Direction: TDirection): Integer; overload;
    procedure Pack;
    procedure Reverse;
    procedure Sort; overload;
    procedure Sort(CompareProc: TListCompareMethod); overload;
    procedure Shuffle;
    procedure TrimExcess;
    property Item[Index: Integer]: Variant read Get write Put; default;
    property Capacity: Integer read GetCapacity write SetCapacity;
    property Count: Integer read GetCount write SetCount;
  end;

后面我们会在介绍具体实现类的时候来详细讲解这些操作。

TAbstractList 类

该类是所有可变类型 List 的基类,它实现了 IList 接口上的枚举器,复制和同步等操作。它继承自 TInterfacedObject,因此它也继承了接口生存周期管理。它是一个抽象类,您不应该对它进行实例化。

如果您打算实现自己的可变类型 List,那么您应该直接或间接的继承自它。因为在 Hprose 序列化和反序列化时,判断一个类是否是可变类型的 List 是通过判断 TAbstractList 是否是这个类的祖先类的方式完成的。

该类上还实现了一个类方法 Split。但因为 TAbstractList 是抽象类,所以您只能在它的可实例化子类上调用它。该方法的具体用法,我们在下面介绍 TArrayList 类时来详细介绍。

IArrayList 接口

  IArrayList = interface(IList)
  ['{0D12803C-6B0B-476B-A9E3-C219BF651BD1}']
  end;

该接口继承自 IList 接口,并且没有添加任何操作。你可能会觉得这样定义比较奇怪,因为在平时使用时,该接口似乎是没有什么用处的。

这样做的原因是为了在反序列化列表类型数据时,可以通过指定该具体的接口,来反序列化为具体的实现类对象。

IArrayList 接口对应 TArrayList 实现。

IHashedList 接口对应 THashedList 实现。

ICaseInsensitiveArrayList 接口对应 TCaseInsensitiveArrayList 实现。

ICaseInsensitiveHashedList 接口对应 TCaseInsensitiveHashedList 实现。

如果反序列化时,指定的类型是 IList 或者 IImmutableList 接口类型,那么反序列化时,也会反序列化为 TArrayList 实现的具体对象。

关于序列化和反序列化的内容,我们会在后面具体的章节中在详细介绍。

TArrayList 类

该类直接继承自 TAbstractList,它是最常用的 List。它内部是通过动态数组实现的。

创建 TArrayList 对象

TArrayList 有多个构造方法,其中有一部分继承自 TAbstractList

constructor Create(ACapacity: Integer = 4; Sync: Boolean = True; ReadWriteSync: Boolean = False); overload; override;
constructor Create(Sync: Boolean; ReadWriteSync: Boolean = False); overload; virtual;
constructor Create(const AList: IImmutableList; Sync: Boolean = True; ReadWriteSync: Boolean = False); overload; virtual;
constructor Create(const Container: Variant; Sync: Boolean = True; ReadWriteSync: Boolean = False); overload; virtual;
constructor Create(const ConstArray: array of const; Sync: Boolean = True; ReadWriteSync: Boolean = False); overload; virtual;

ACapacity 参数是初始化容量,如果您事先确知要放入的元素个数,那么将容量设置为与元素个数相同或多于元素个数,可以在后面放入元素时,避免内存重新分配。该参数默认值为 4。

初始化容量不代表这个 List 只能放多少个元素,也不代表这个 List 有多少个元素。它只代表在不重新分配内存的情况下,能放入的元素个数。当元素个数增长到超过这个容量时,这个容量会自动扩大,您不需要做任何特殊操作。

Sync 表示是否默认创建带有线程同步锁的 List 对象。默认为 True,如果你不需要在程序对该 List 做 LockUnLock 操作,那么你可以将该参数设置为 False。即使创建了不带锁的 List 对象,后面还可以通过调用该对象的 InitLock 方法来初始化锁,之后 LockUnLock 方法也可以正常使用。

ReadWriteSync 是表示是否默认创建带多读单写锁的 List 对象。,如果需要该功能,在通过构造函数创建对象时,只需要将 ReadWriteSync 参数设置为 True 即可,该参数默认为 False。之后可以通过 BeginReadEndReadBeginWriteEndWrite 这四个方法来操作读写锁。也可以在创建不带锁的对象后,单独调用 InitReadWriteLock 来初始化多读单写锁。

创建 List 对象时,可以设置初始值,初始值可以是一个 IImmutableList 对象(即所有的 Hprose 中定义的 List 对象),或者一个 Variant 类型的变量,该变量中保存的值需要是一个 List 对象或者是一个动态数组,如果是其它类型的 Variant 类型的变量,将会被忽略。初始值还支持 array of const 类型的参数。

通常创建 TArrayList 对象之后,不应该直接保存为 TArrayList 类型的对象,这样需要自己手动管理内存,更方便的方法是把返回的对象保存为一个 IList 的接口变量中。Hprose 还提供了一个简单的创建带有初始值的函数:

function ArrayList(const Elements: array of const): IArrayList;

例如:

procedure CreateDemo;
var
  L: IArrayList;
  I: Integer;
begin
  L := ArrayList([1, 2, 3]);
  for I := 0 to L.Count - 1 do
    Writeln(L[I]);
end;

Hprose 最新的版本还支持将一个 IList 保存到 Variant 变量中,并直接在这个变量上操作 IList 上的属性和方法。不过需要注意的是,不能直接在 Varaint 变量上像操作数组那样直接通过下标来存取元素,例如:

procedure CreateDemo2;
var
  L: Variant;
  I: Integer;
begin
  L := ArrayList([1, 2, 3]);
  for I := 0 to L.Count - 1 do
    Writeln(L.Get(I));
end;

这里获取元素需要用 Get 方法。

添加元素

您可以通过 Add 方法一次添加一个元素,或通过 AddAll 方法一次添加多个元素。看下面的例子:

procedure AddDemo;
var
  List, List2: IList;
  I: Integer;
begin
  List := TArrayList.Create;
  List.Add(1);
  List.Add('abc');
  List.Add(3.14);
  List.Add(True);
  List2 := TArrayList.Create;
  List2.AddAll(List);
  List2.AddAll(['a','b','c']);
  for I := 0 to List2.Count - 1 do Writeln(List2[I]);
  List2.Add(List);
  List := VarToList(List2.Last());
  for I := 0 to List.Count - 1 do Writeln(List[I]);
end;

通过这个例子,您会发现可以添加任意类型(可转换为 Variant 类型的类型)的数据到 IList 对象中,甚至 IList 对象本身也可以作为元素添加到 IList 对象中,并且可通过 AddAllIList 对象或者动态数组中的元素批量添加到 IList 对象中。

上面例子的运行结果为:

1
abc
3.14
True
a
b
c
1
abc
3.14
True

上面代码中我们使用 List2.Last 方法来获取列表的最后一个元素,它相当于 List2[List2.Count - 1],但是写起来更简单。另外,与之对应的,还有一个 First 方法可以用来获取第一个元素。

插入元素

使用 Insert 方法可以在列表的任何位置插入元素,例如:

procedure InsertDemo;
var
  List: IList;
  I: Integer;
begin
  List :=  ArrayList([1, 'abc', 3.14, True]);
  List.Insert(0, 'top');
  List.Insert(3, 'middle');
  List.Insert(6, 'bottom');
  for I := 0 to List.Count - 1 do Writeln(List[I]);
end;

运行结果如下:

top
1
abc
middle
3.14
True
bottom

另外,你还可以通过 InsertRange 方法来一次插入一组数据,例如:

procedure InsertRangeDemo;
var
  List: IList;
  I: Integer;
begin
  List := ArrayList([1, 'abc', 3.14, True]);
  List.InsertRange(2, [1, 2, 3]);
  for I := 0 to List.Count - 1 do Writeln(List[I]);
end;

运行结果如下:

1
abc
1
2
3
3.14
True

移动元素

通过 Move 方法可以将一个元素从一个位置移动到另一个位置,例如:

procedure MoveDemo;
var
  List: IList;
  I: Integer;
begin
  List := ArrayList([1, 'abc', 3.14, True]);
  List.Move(2, 0);
  for I := 0 to List.Count - 1 do Writeln(List[I]);
end;

运行结果如下:

3.14
1
abc
True

交换元素

通过 Exchange 方法可以将一个元素从一个位置移动到另一个位置,例如:

procedure ExchangeDemo;
var
  List: IList;
  I: Integer;
begin
  List := ArrayList([1, 'abc', 3.14, True]);
  List.Exchange(2, 0);
  for I := 0 to List.Count - 1 do Writeln(List[I]);
end;

运行结果如下:

3.14
abc
1
True

查找元素

通过 Contains, IndexOfLastIndexOf 方法我们可以确认一个元素是否在 IList 对象,例如:

procedure FindDemo;
var
  List: IList;
begin
  List := ArrayList([1, 'abc', 3.14, True, 'abc']);
  Writeln(List.Contains('hello'));
  Writeln(List.Contains('abc'));
  Writeln(List.IndexOf(3.14));
  Writeln(List.IndexOf(False));
  Writeln(List.LastIndexOf('abc'));
  Writeln(List.LastIndexOf(1));
end;

运行结果如下:

FALSE
TRUE
2
-1
4
0

通过上面的例子我们还可以看出 Contains, IndexOfLastIndexOf 方法的不同之处。Contains 方法只返回存在与否。IndexOf 方法会返回具体位置,如果不存在,则返回 -1LastIndexOf 方法也是返回具体的位置,但是它是从后往前查找。

另外,IndexOfLastIndexOf 方法还可以指定开始查找位置 StartIndex,以及从开始查找位置起要检查的元素个数 ACount

这三个方法在 TArrayList 中的时间复杂度都是 O(n),这与后面将要介绍的 THashedListTCaseInsensitiveHashedList 是不同的,后面我们在介绍它们时再举例说明。

删除元素

通过 DeleteRemove 都可以删除元素。

Delete 是根据索引删除并返回删除的元素,例如:

procedure DeleteDemo;
var
  List: IList;
begin
  List := ArrayList([1, 'abc', 3.14, True]);
  Writeln(List.Delete(2));
  Writeln(List.Delete(2));
end;

运行结果为:

3.14
True

Remove 是根据元素删除并返回删除的索引,并且可以设置查找元素的方向,例如:

procedure RemoveDemo;
var
  List: IList;
begin
  List := ArrayList([1, 'abc', 3.14, True, 'abc']);
  Writeln(List.Remove('abc', FromEnd));
  Writeln(List.Remove('abc', FromBeginning));
  Writeln(List.Remove(True));
end;

运行结果为:

4
1
2

通过上面的结果,我们还会发现,元素删除后,后面的元素会自动往前移动。

与插入元素类似,删除元素也提供了一个 DeleteRange 方法用于一次删除一组元素。Index 参数是开始删除的位置,ACount 是要删除的元素个数。它比你使用 Delete 一个一个删除要快得多。

清空

通过 Clear 方法可以清空所有元素,如果要删除 IList 对象中的所有元素,使用 Clear 比使用 DeleteDeleteRange 都快很多。

例如:

procedure ClearDemo;
var
  List: IList;
begin
  List := ArrayList([1, 'abc', 3.14, True]);
  List.Clear;
  Writeln(List.Count);
end;

结果为:

0

复制

通过 Assign 方法可以将本对象变为指定对象一个副本。该方法非常简单,这里不再举例说明。

同步

当您在多个线程中操作同一个 IList 对象时,为了保证数据的线程安全性,需要对这个对象进行同步操作。通过 LockUnLock 方法可以非常方便的实现对 IList 对象自身的线程同步。只需要在操作该对象前,调用该对象的 Lock 方法,所有操作完成后,再调用 Unlock 方法即可。

如果你在对共享的 IList 对象进行读写操作时,读操作远远多于写操作,可以使用多读单写锁来提高性能。这部分在 创建 TArrayList 对象一节已经介绍了,这里就不在重复了。

转换为数组

通过 ToArray 方法,可以将 IList 对象直接转换为动态数组。例如:

procedure ToArrayDemo;
var
  List: IList;
  A: array of Integer;
  I: Integer;
begin
  List := ArrayList([1, 3, 4, 5, 9]);
  A := List.ToArray(varInteger);
  for I := 0 to Length(A) - 1 do Writeln(A[I]);
end;

运行结果如下:

1
3
5
7
9

for...in 支持

如果您使用 Delphi 2005 或更高版本,或者 FreePascal 2.5.1 或更高版本,您可以通过 for...in 语句来枚举 IList 接口的元素:

procedure ForInDemo;
var
  List: IList;
  V: Variant;
begin
  List := ArrayList([1, 'abc', 3.14, True]);
  for V in List do Writeln(V);
end;

分割字符串

通过 TArrayList 上的类方法 Split,可以直接将一个字符串分割为 TArrayList 对象。Split 有 7 个参数,这 7 个参数的含义分别是:

  1. Str 表示待分割的字符串。
  2. Separator 表示分割符,默认值是逗号(,),它可以是单个字符,也可以是字符串。如果该分割符为空字符串,则返回只包含一个完整 Str 元素的 TArrayList 对象。
  3. Limit 表示最多分割为几个元素,默认值是 0,表示没有限制。
  4. TrimItem 表示是否去掉分割后的字符串的首尾空格,默认为 False
  5. SkipEmptyItem 表示是否忽略空串元素,默认为 False
  6. SyncTArrayList 构造方法的 Sync 参数意义相同。
  7. ReadWriteSyncTArrayList 构造方法的 ReadWriteSync 参数意义相同。

例如:

procedure PrintList(List: IList);
var
  I: Integer;
begin
  for I := 0 to List.Count - 1 do Writeln(List[I]);
  Writeln('--------');
end;

procedure SplitDemo;
var
  S: String;
begin
  S := 'Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday,';
  PrintList(TArrayList.Split(S));
  PrintList(TArrayList.Split(S, ', '));
  PrintList(TArrayList.Split(S, ',', 4));
  PrintList(TArrayList.Split(S, ',', 0, true));
  PrintList(TArrayList.Split(S, ',', 0, true, true));
end;

该程序的运行结果为:

Monday
 Tuesday
 Wednesday
 Thursday
 Friday
 Saturday
 Sunday

--------
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday,
--------
Monday
 Tuesday
 Wednesday
 Thursday, Friday, Saturday, Sunday,
--------
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday

--------
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
--------

合并字符串

天下大势,分久必合,合久必分。前面讲了分割字符串,那肯定就少不了合并字符串。

如果 IList 对象中所有的元素都是字符串,或者都可以转换为字符串类型,那么就可以通过 Join 方法,将这些元素以指定的方式连接成一个字符串。Join 方法有 3 个参数:

  1. Glue 参数是用于合并两个字符串之间字符串,默认是逗号(,)。
  2. LeftPad 参数是补在合并之后的字符串开头的字符串,默认是空字符串。
  3. RightPad 参数是补在合并之后的字符串结尾的字符串,默认是空字符串。

例如:

procedure JoinDemo;
var
  S: String;
  List: IList;
begin
  S := 'Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday';
  List := TArrayList.Split(S);
  Writeln(List.Join);
  Writeln(List.Join('; '));
  Writeln(List.Join('", "', '["', '"]'));
end;

运行结果为:

Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday
Monday; Tuesday; Wednesday; Thursday; Friday; Saturday; Sunday
["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]

存取元素

可以通过 Item 属性来存取 IList 中的元素,因为 Item 为默认属性,所以 Item 可省略。也就是说,可以用存取数组元素的方法来存取 IList 的元素。

但如果是将 IList 对象保存在了 Variant 变量中,则只能使用 GetPut 方法来存取元素。

当使用 Item 属性或者 Put 方法来放入元素时,如果下标超过原最大下标,只要没有超过 MaxListSize,列表会自动扩容。但如果下标小于 0 或者超过 MaxListSize 则会抛出异常。

当使用 Item 属性或者 Get 方法来获取元素时,如果下标越界会返回 Unassigned 这样一个 Variant 值,不会抛出异常。

例子暂且不举,下面这个方法中你会马上看到例子。

压缩

IList 提供了一个 Pack 方法用于把 IList 对象中所有值不是 Unassigned 的元素聚在一起,同时把 Count 的属性值改变,这样,所有值为 Unassigned 的元素就会被删除。但是该操作并不会减少 Capacity,如果你想把没用的空间都释放掉的话,可以再调用 TrimExcess 方法,它会把 Capacity 设置成 Count

procedure PackDemo;
var
  L: IList;
  N: Integer;
begin
  L := ArrayList([1, 'abc', 3.14, True]);
  L[10] := 'test';
  Writeln(L.Count);
  Writeln(L[10]);
  N := L.Capacity;
  L.Pack;
  Writeln(L.Count);
  Writeln(L[4]);
  Writeln(L.Capacity = N);
  L.TrimExcess;
  Writeln(L.Capacity = N);
  Writeln(L.Count = L.Capacity);
end;

运行结果为:

11
test
5
test
True
False
True

翻转

如果你希望把列表中的元素位置翻转,可以使用 Reverse 方法,例如:

procedure ReverseDemo;
var
  List: IList;
begin
  List := ArrayList([1, 'abc', 3.14, True]);
  List.Reverse;
  Writeln(List.Join);
end;

运行结果为:

True,3.14,abc,1

排序

如果列表中的元素是可排序的,那么可以通过 Sort 方法进行排序。所谓可排序是指任意两个元素之间都是可以比较大小的。默认是按照从小到大的顺序排列的,你也可以自己定制排序比较方法。

procedure SortDemo;
var
  List: IList;
begin
  List := ArrayList([3, 5, 1, 2, 4, 9, 7, 6, 8]);
  List.Sort;
  Writeln(List.Join);
end;

运行结果为:

1,2,3,4,5,6,7,8,9

洗牌

除了可以把 IList 中的元素进行排序,还可以把元素的顺序打乱,使用 Shuffle 方法可以将 IList 中的元素顺序随机打乱,洗牌算法跟排序算法不同,洗牌算法不要求元素之间是可比较的。例如:

procedure ShuffleDemo;
var
  List: IList;
begin
  List := ArrayList([1, 'abc', 3.14, True]);
  List.Shuffle;
  Writeln(List.Join);
end;

该程序运行结果几乎每次都不相同。

THashedList 类

该类继承自 TArrayList。它内部增加了一个 HashBucket 来管理每个元素的 Hash 值与索引之间的关系。这使得在该类的对象上执行ContainsIndexOfLastIndexOfRemove 操作的时间复杂度为 O(1),而 TArrayList 上执行这些个操作的时间复杂度为 O(n)。

虽然在包含大量元素时,使用 THashedList 进行反查索引操作要比 TArrayList 快的多,但是 THashedList 也会占用更多的空间,并且在进行其它操作时,比 TArrayList 要稍慢一些,因此,只有当你需要快速反查索引时,才有必要使用 THashedList

它的操作和效果与 TArrayList 完全相同。这里就不在单独举例了。

TCaseInsensitiveArrayList 类

该类继承自 TArrayList,因此在该类的对象上执行 ContainsIndexOfLastIndexOfRemove 操作的时间复杂度为 O(n)。但与 TArrayList 不同的是,如果在执行 ContainsIndexOfLastIndexOfRemove 操作时,查找的是字符串元素,该类是不区分大小写的。

它的其它操作的效果与 TArrayList 完全相同。这里也不在单独举例了。

TCaseInsensitiveHashedList 类

该类继承自 THashedList,因此在该类的对象上执行 ContainsIndexOfLastIndexOfRemove 操作的时间复杂度为 O(1)。但与 THashedList 不同的是,如果在执行 ContainsIndexOfLastIndexOfRemove 操作时,查找的是字符串元素,该类是不区分大小写的。

它的其它操作的效果与 THashedList 完全相同。这里也不在单独举例了。