zoukankan      html  css  js  c++  java
  • Delphi中的容器类 *List ( 二)

     THashedStringList类  一般来说,通过键来查找值最简单的办法是遍历列表对列表中的键进行比较,如果相等则获取相应的键值。但是这种简单的办法也是效率最差的一种办法,当列表中的项目比较少时,这种办法还可以接受,但是如果列表中项目非常多的话,这种方法会极大的影响软件 运行速度。 这时我们可以使用哈希表来快速的通过键值来存取列表中的元素。由于本书并不是一本数据结构和算法的书,因此我无意在这里讨论哈希表背后的理论知识,我们只 要知道哈希可以通过键快速定位相应的值就可以了,对此感兴趣的非计算机专业的人可以去察看相关的书,这里就不赘述了。

      Delphi6中提供的THashedStringList类没有提供任何的新的方法,只是对IndexOfIndexOfName函数通过哈希表进行了性能优化,下面这个例子演示了TStringListTHashedStringList之间的性能差异:

    unit CHash;
    interface
    uses
     Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
     Dialogs, StdCtrls, Inifiles;
    type
     TForm1 = class(TForm)
      Button1: TButton;
      procedure Button1Click(Sender: TObject);
      procedure FormCreate(Sender: TObject);
      procedure FormDestroy(Sender: TObject);
     private
      { Private declarations }
      HashedList: THashedStringList;
      DesList: TStringList;
      List: TStringList;
     public
      { Public declarations }
      procedure Hash;
      procedure Iterate;
     end;
    var
     Form1: TForm1;
    implementation
    {$R *.dfm}
    procedure TForm1.Button1Click(Sender: TObject);
    var
     I:Integer;
    begin
     Screen.Cursor := crHourGlass;
     try
    //
    初始化系统
      for I := 0 to 5000 do
      begin
       HashedList.Add(IntToStr(i));
       List.Add(IntToStr(i));
      end;
      Hash;
      DesList.Clear;
      Iterate;
     finally
      Screen.Cursor := crDefault;
     end;
    end;
    procedure TForm1.Hash;
    var
     I, J: Integer;
    begin
     //基于哈希表的定位
     for I := 3000 to 4000 do
     begin
      DesList.Add(IntToStr(HashedList.IndexOf(IntToStr(I))));
     end;
    end;
    procedure TForm1.Iterate;
    var
     I, J: Integer;
    begin
     //基于遍历方式定位
     for I := 3000 to 4000 do
     begin
      DesList.Add(IntToStr(List.IndexOf(IntToStr(I))));
     end;
    end;
    procedure TForm1.FormCreate(Sender: TObject);
    begin
     HashedList := THashedStringList.Create;
     DesList := TStringList.Create;
     List := TStringList.Create;
    end;
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
     HashedList.Free;
     DesList.Free;
     List.Free;
    end;
    end.

       上面代码中的Hash过程,采用了新的THashedStringList类来实现的查找,而Iterate过程中使用了原来的TStringList 类的IndexOfName来实现的查找。采用GpProfile(注:GpProfile的用法参见工具篇的性能分析工具GpProfile章节)对两 个过程进行了性能比较后,从下图可以看到Hash执行同样查找动作只用了0.7%的时间,而Iterate方法则用了99.3%的时间,可以看到在字符串 列表项目数在几千的数量级别时,基于哈希表的查询速度是原有方法的100多倍。

    Delphi中的容器类 <wbr>*List <wbr>( <wbr>二)

       不过要说明的是,THashedStringListTStringList类相比,虽然查找的速度大大提高了,但是在添加、删除字符串后再次进行查 找操作时,需要重新计算哈希函数,所以如果频繁的进行删除或者添加同查找的复合操作,执行的速度很有可能比TStringList还要慢,这是使用时需要 注意的

      TBucketListTObjectBucketList

      从Delphi6开始,VCLContnrs单元中又增加了两个新的容器类TBucketListTObjectBucketListTBucketList实际上也是一个简单基于哈希表的指针-指针对列表。接口定义如下:

     TBucketList = class(TCustomBucketList)

     public
      destructor Destroy; override;
      procedure Clear;
      function Add(AItem, AData: Pointer): Pointer;
      function Remove(AItem: Pointer): Pointer;
      function ForEach(AProc: TBucketProc; AInfo: Pointer = nil): Boolean;
      procedure Assign(AList: TCustomBucketList);
      function Exists(AItem: Pointer): Boolean;
      function Find(AItem: Pointer; out AData: Pointer): Boolean;
      property Data[AItem: Pointer]: Pointer read GetData write SetData; default;
     end;

       类的Add方法现在接受两个参数AItemAData,我们可以把它看成是指针版的Map实现(从容器类来看, Delphi从语言的灵活性来说不如C++,为了实现不同类型的哈希Map容器,Delphi需要派生很多的类,而C++Map是基于模版技术来实现 的,容器元素的类型只要简单的声明一下就能指定了,使用起来非常方便。而从简单性来说,则不如Java的容器类,因为Delphi中的String是原生 类型,而不是类,并且Delphi还提供对指针的支持,因此要为指针和字符串提供不同的Map派生类),类中的ExistsFind等方法都是通过哈希 表来实现快速数据定位的。同时,同一般的列表容器类不同,TBucketList不提供通过整数下标获取列表中的元素的功能,不过我们可以使用ForEach方法来遍历容器内的元素。

      TObjectBucketList是从TBucketList派生的基类,没有增加任何新的功能,唯一的不同之处就是容器内的元素不是指针而是对象了,实现了更强的类型检查而已

      其它容器类

      TThreadList

       TThreadList类实际上就是一个线程安全的TList类,每次添加或者删除容易中指针时,TThreadList会调用 EnterCriticalSection函数进入线程阻塞状态,这时其它后续发生的对列表的操作都会阻塞在那里,直到TThreadList调用 UnLockList释放对列表的控制后才会被依次执行。在多线程开发中,我们需要使用TThreadList来保存共享的资源以避免多线程造成的混乱和 冲突。还要注意的是TThreadList有一个Duplicates布尔属性,默认为True,表示列表中不能有重复的指针。设定为False将允许容 器内有重复的元素。

      TInterfaceList

      在Classes单元中,VCL还定义了一个可以保存接口的列表类。我们可以向列表中添加接口类型,这个类的操作方法同其它的列表类没有什么区别,只不过在内部使用TThreadList作为容器实现了线程安全。

      拟容器类TBits

      在Classes.pas还有一个特殊的TBits类,接口定义如下:

     TBits = class

     public
      destructor Destroy; override;
      function OpenBit: Integer;
      property Bits[Index: Integer]: Boolean read GetBit write SetBit; default;
      property Size: Integer read FSize write SetSize;
     end;

      它可以按位储存布尔值,因此可以看成是一个原生的Boolean值的容器类,但是它缺少列表类的很多方法和特性,不能算是一个完整的容器,因此我们称它为拟容器类。

      在我们开发过程中,经常需要表示一些类似于开关的二元状态,这时我们用TBits来表示一组二元状态非常方便,同时TBits类的成员函数主要是用汇编 言写的,位操作的速度非常快。二元状态组的大小通过设定TBits类的Size属性来动态的调整,存取Boolean值可以通过下标来存取TBits类的 Bits属性来实现。至于OpenBit函数,它返回第一个不为TrueBoolean值的下标。从接口定义可以看出,TBits类接口非常简单,提供 的功能也很有限,我猜测这只是Borland的研发队伍满足内部开发有限需要的类,并不是作为一个通用类来设计的,比如它没有开放内部数据存取的接口,无 法获得内部数据的表达,进而无法实现对状态的保存和加载等更高的需求。

      TCollection

      前面我们提到了 DelphiIDE能够自动将字符串列表保存在DFM文件中,并能在运行时将设计期编辑的字符串列表加载进内存(也就是我们通常所说的类的可持续性)。 TStrings这种特性比较适合于保存一个对象同多个字符串数据之间关联,比较类似于现实生活中一个人同多个Email账户地址之间的关系。但 是,TStrings类型的属性有一个很大的局限那就是,它只能用于设计时保存简单的字符串列表,而不能保存复杂对象列表。而一个父对象同多个子对象之间 的聚合关系可能更为常见,比如一列火车可能有好多节车厢构成,每节车厢都有车厢号,车厢类型(卧铺,还是硬座),车厢座位数,车厢服务员名称等属性构成。 如果我们想在设计期实现对火车的车厢定制的功能,并能保存车厢的各个属性到窗体文件中,则车厢集合属性定义为TStrings类型的属性是行不通的。

      对于这个问题,Delphi提供了TCollection容器类属性这样一个解决方案TCollection以及它的容器元素TCollectionItem的接口定义如下:

     TCollection = class(TPersistent)
     
     protected
      procedure Added(var Item: TCollectionItem); virtual; deprecated;
      procedure Deleting(Item: TCollectionItem); virtual; deprecated;
      property NextID: Integer read FNextID;
      procedure Notify(Item: TCollectionItem; Action: TCollectionNotification); virtual;
      { Design-time editor support }
      function GetAttrCount: Integer; dynamic;
      function GetAttr(Index: Integer): string; dynamic;
      function GetItemAttr(Index, ItemIndex: Integer): string; dynamic;
      procedure Changed;
      function GetItem(Index: Integer): TCollectionItem;
      procedure SetItem(Index: Integer; Value: TCollectionItem);
      procedure SetItemName(Item: TCollectionItem); virtual;
      procedure Update(Item: TCollectionItem); virtual;
      property PropName: string read GetPropName write FPropName;
      property UpdateCount: Integer read FUpdateCount;
     public
      constructor Create(ItemClass: TCollectionItemClass);
      destructor Destroy; override;
      function Owner: TPersistent;
      function Add: TCollectionItem;
      procedure Assign(Source: TPersistent); override;
      procedure BeginUpdate; virtual;
      procedure Clear;
      procedure Delete(Index: Integer);
      procedure EndUpdate; virtual;
      function FindItemID(ID: Integer): TCollectionItem;
      function GetNamePath: string; override;
      function Insert(Index: Integer): TCollectionItem;
      property Count: Integer read GetCount;
      property ItemClass: TCollectionItemClass read FItemClass;
      property Items[Index: Integer]: TCollectionItem read GetItem write SetItem;
     end;
     TCollectionItem = class(TPersistent)

     protected
      procedure Changed(AllItems: Boolean);
      function GetOwner: TPersistent; override;
      function GetDisplayName: string; virtual;
      procedure SetCollection(Value: TCollection); virtual;
      procedure SetIndex(Value: Integer); virtual;
      procedure SetDisplayName(const Value: string); virtual;
     public
      constructor Create(Collection: TCollection); virtual;
      destructor Destroy; override;
      function GetNamePath: string; override;
      property Collection: TCollection read FCollection write SetCollection;
      property ID: Integer read FID;
      property Index: Integer read GetIndex write SetIndex;
      property DisplayName: string read GetDisplayName write SetDisplayName;
     end;

       TCollection类是一个比较复杂特殊的容器类。但是初看上去,它就是一个TCollectionItem对象的容器类,同列表类TList 似,TCollection类也维护一个TCollectionItem对象索引数组,Count属性表示容器中包含的TCollectionItem 数目,同时也提供了AddDelete方法来添加和删除TCollectionItem对象以及通过下标存取TCollectionItem的属性。看 上去和容器类区别不大,但是在VCL内部用于保存和加载组件的TReaderTWriter类提供了两个特殊的方法WriteCollection ReadCollection用于加载和保存TCollection类型的集合属性。IDE就是通过这两个方法实现对TCollection类型属性的可 持续性。

      假设现在需要设计一个火车组件TTrainTTrain组件有一个TCollection类型的属性Carriages表示 多节车厢构成的集合属性,每个车厢则对应于集合属性的元素,从TCollectionItem类继承,有车厢号,车厢类型(卧铺,还是硬座),车厢座位 数,车厢服务员名称等属性,下面是我设计的组件的接口:

    type
     //车厢类型,硬座、卧铺
     TCarriageType = (ctHard, ctSleeper);
     //车厢类
     TCarriageCollectionItem = class(TCollectionItem)

     published
      //车厢号码
    property CarriageNum: Integer read FCarriageNum write FCarriageNum;
    //
    座位数
    property SeatCount: Integer read FSeatCount write FSeatCount;
    //
    车厢类型
    property CarriageType: TCarriageType read FCarriageType write FCarriageType;
    //
    服务员名称
      property ServerName: string read FServerName write FServerName;
     end;
     TTrain=class;
     //车厢容器属性类 
     TCarriageCollection = class(TCollection)
     private
      FTrain:TTrain;
      function GetItem(Index: Integer): TCarriageCollectionItem;
      procedure SetItem(Index: Integer; const Value: TCarriageCollectionItem);
     protected
      function GetOwner: TPersistent; override;
     public
      constructor Create(ATrain: TTrain);
      function Add: TCarriageCollectionItem;
    property Items[Index: Integer]: TCarriageCollectionItem read GetItem
    write SetItem; default;
     end;
     //火车类
     TTrain = class(TComponent)
     private
      FItems: TCarriageCollection;
      procedure SetItems(Value: TCarriageCollection);
     public
      constructor Create(AOwner: TComponent); override;
      destructor Destroy; override;
     published
      property Carriages: TCarriageCollection read FItems write SetItems;
     end;

      其中车厢类的定义非常简单,只是定义了四个属性。而车厢集合类重定义了静态的Add方法以及Items属性,其返回结果类型改为了TCarriageCollectionItem,下面是车厢集合类的实现代码:

    function TCarriageCollection.Add: TCarriageCollectionItem;
    begin
     Result:=TCarriageCollectionItem(inherited Add);
    end;
      
    constructor TCarriageCollection.Create(ATrain: TTrain);
    begin
     inherited Create(TCarriageCollectionItem);
     FTrain:=ATrain;
    end;
      
    function TCarriageCollection.GetItem(
     Index: Integer): TCarriageCollectionItem;
    begin
     Result := TCarriageCollectionItem(inherited GetItem(Index));
    end;
      
    function TCarriageCollection.GetOwner: TPersistent;
    begin
     Result:=FTrain;
    end;
      
    procedure TCarriageCollection.SetItem(Index: Integer;
     const Value: TCarriageCollectionItem);
    begin
     inherited SetItem(Index, Value);
    end;

       其中Add,GetItemSetItem都非常简单,就是调用基类的方法,然后将基类的方法的返回结果重新映射为TCollectionItem 型。而构造函数中将TTrain组件作为父组件传入,并重载GetOwner方法,返回TTrain组件,这样处理的原因是IDE会在保存集合属性时调用 集合类的GetOwner确认属性的父控件是谁,这样才能把集合属性写到DFM文件中时,才能存放到正确的位置下面,建立正确的聚合关系。

      而火车组件的实现也非常简单,只要定义一个Published Carriages属性就可以了,方法实现代码如下:

    constructor TTrain.Create(AOwner: TComponent);
    begin
     inherited;
     FItems := TCarriageCollection.Create(Self);
    end;
      
    destructor TTrain.Destroy;
    begin
     FItems.Free;
     inherited;
    end;
      
    procedure TTrain.SetItems(Value: TCarriageCollection);
    begin
     FItems.Assign(Value);
    end;

      下面将我们的组件注册到系统面板上之后,就可以在窗体上放上一个TTrain组件,然后然后选中Object Inspector,然后双击Carriages属性,会显示系统默认的集合属性编辑器,使用Add按钮向列表中添加两个车厢,修改一下属性,如下图所示意:

    Delphi中的容器类 <wbr>*List <wbr>( <wbr>二)

       从上面的属性编辑器我们,可以看到默认情况下,属性编辑器列表框是按项目索引加上一个横杠来显示车厢的名称,看起来不是很自然。要想修改显示字符串,需 要重载TCarriageCollectionItemGetDisplayName方法。修改后的GetDisplayName方法显示车厢加车厢号 码:

    function TCarriageCollectionItem.GetDisplayName: string;
    begin
     Result:='车厢'+IntToStr(CarriageNum);
    end;

      示意图:

    Delphi中的容器类 <wbr>*List <wbr>( <wbr>二)

      保存一下文件,使用View As Text右键菜单命令察看一下DFM文件,我们会看到我们设计的车厢类的属性确实都被写到了DFM文件中,并且Carriages属性的父亲就是Train1

     object Train1: TTrain
      Carriages = <
       item
        CarriageNum = 1
        SeatCount = 100
        CarriageType = ctHard
        ServerName = '陈省'
       end
       item
        CarriageNum = 2
        SeatCount = 200
        CarriageType = ctHard
        ServerName = 'hubdog'
       end>
      Left = 16
      Top = 8
     End

      TOwnedCollection

       从Delphi4开始,VCL增加了一个TOwnedCollection类,它是TCollection类的子类,如果我们的 TCarriageCollection类是从TOwnedCollection类继承的,这时我们就不再需要向上面重载GetOwner方法并返回父控 件给IDE,以便TCarriageCollection属性能出现在Object Inspector中了。

      总结

       本章中我介绍了几乎所有VCL中重要的容器类,其中TList及其子类相当于通用的容器类,虽然不如C++Java功能那么强大,但是用好了已经足以满 足我们90%的开发需要,而TStrings及其子类,还有TCollection则是实现所见即所得设计的关键类,对于开发灵活强大的自定义组件来说是 必不可少的。

  • 相关阅读:
    Echrarts的基本API
    Echarts中Option属性设置
    大数据ETL处理时遇到的坑
    模块设计模式
    PAT (Advanced Level) Practice 代码
    递归中的 DFS 与 DP 比较
    CF 1557 D. Ezzat and Grid
    CF 1557 C. Moamen and XOR
    CF 1555 E. Boring Segments
    CF 1555 D. Say No to Palindromes
  • 原文地址:https://www.cnblogs.com/luckForever/p/7255131.html
Copyright © 2011-2022 走看看