Delphi中的容器類


從Delphi 5開始VCL中增長了一個新的Contnrs單元,單元中定義了個新的類,所有都是基於標準的TList 類。

TList 類

   TList 類實際上就是一個能夠存儲指針的容器類,提供了一系列的方法和屬性來添加,刪除,重排,定位,存取和排序容器中的類,它是基於數組的機制來實現的容器,比較相似於C++中的Vector和Java中的 ArrayList,TList 常常用來保存一組對象列表,基於數組實現的機制使得用下標存取容器中的對象很是快,可是隨着容器中的對象的增多,插入和刪除對象速度會直線降低,所以不適合頻繁添加和刪除對象的應用場景。下面是TList類的屬性和方法說明:

屬性       描述

Count: Integer;    返回列表中的項目數

Items[Index: Integer]: Pointer; default    經過以爲底的索引下標直接存取列表中的項目

方法  類型    描述

Add(Item: Pointer): Integer;   函數   用來向列表中添加指針

Clear;  過程  清空列表中的項目

Delete(Index: Integer);  過程    刪除列表中對應索引的項目

IndexOf(Item: Pointer): Integer;   函數   返回指針在列表中的索引

Insert(Index: Integer; Item: Pointer);  過程   將一個項目插入到列表中的指定位置

Remove(Item: Pointer): Integer;   函數   從列表中刪除指針

名稱      類型      描述

Capacity: Integer;  property    能夠用來獲取或設定列表能夠容納的指針數目

Extract(Item: Pointer): Pointer;   function    Extract 相似於Remove 能夠將指針從列表中刪除,不一樣的是返回被刪除的指針。

Exchange(Index1, Index2: Integer);    procedure   交換列表中兩個指針

First: Pointer;   function    返回鏈表中的第一個指針

Last: Pointer;   function    返回鏈表中最後一個指針

Move(CurIndex NewIndex: Integer);    procedure    將指針從當前位置移動到新的位置

Pack;    procedure     從列表中刪除全部nil指針

Sort(Compare: TListSortCompare);    procedure    用來對鏈表中的項目進行排序,能夠設定Compare參數爲用戶定製的排序函數

 

TObjectList 類

  TObjectList 類直接從TList 類繼承,能夠做爲對象的容器。TObjectList類定義以下:

TObjectList = class(TList) 
... 
public 
constructor Create; overload; 
constructor Create(AOwnsObjects: Boolean); overload; 
function Add(AObject: TObject): Integer; 
function Remove(AObject: TObject): Integer; 
function IndexOf(AObject: TObject): Integer; 
function FindInstanceOf(AClass: TClass; 
AExact: Boolean = True; AStartAt: Integer = 0): 
Integer; 
procedure Insert(Index: Integer; AObject: TObject); 
property OwnsObjects: Boolean; 
property Items[Index: Integer]: TObject; default; 
end;

不一樣於TList類,TObjectList類的 Add, Remove, IndexOf, Insert等方法都須要傳遞TObject對象做爲參數,因爲有了編譯期的強類型檢查,使得TObjectList比TList更適合保存對象。此外 TObjectList對象有OwnsObjects屬性。當設定爲True (默認值),同TList類不一樣,TObjectList對象將銷燬任何從列表中刪除的對象。不管是調用Delete, Remove, Clear 方法,仍是釋放TObjectList對象,都將銷燬列表中的對象。有了TObjectList類,咱們就不再用使用循環來釋放了對象。這就避免了釋放鏈表對象時,因爲忘記釋放鏈表中的對象而致使的內存泄漏。另外要注意的是OwnsObjects屬性不會影響到Extract方法,TObjectList的Extract方法行爲相似於TList,只是從列表中移除對象引用,而不會銷燬對象。

TObjectList 對象還提供了一個FindInstanceOf 函數,能夠返回只有指定對象類型的對象實例在列表中的索引。若是AExact 參數爲True,只有指定對象類型的對象實例會被定位,若是AExact 對象爲False,AClass 的子類實例也將被定位。AStartAt 參數能夠用來找到列表中的多個實例,只要每次調用FindInstanceOf 函數時,將起始索引加,就能夠定位到下一個對象,直到FindInstanceOf 返回-1。下面是代碼示意:

var 
idx: Integer; 
begin 
idx := -1; 
repeat 
idx := ObjList.FindInstanceOf(TMyObject, True, idx+1); 
if idx >= 0 then 
... 
until(idx < 0); 
end;


TComponentList 類

   Contnrs單元中還定義了TComponentList 類,類定義以下:

TComponentList = class(TObjectList) 
... 
public 
function Add(AComponent: TComponent): Integer; 
function Remove(AComponent: TComponent): Integer; 
function IndexOf(AComponent: TComponent): Integer; 
procedure Insert(Index: Integer; AComponent: TComponent); 
property Items[Index: Integer]: TComponent; default; 
end;

注意TComponentList 是從TObjectList類繼承出來的,它的Add, Remove, IndexOf, Insert和 Items 方法調用都使用TComponent 類型的參數而再也不是TObject類型,所以適合做爲TComponent對象的容器。TComponentList 類還有一個特殊的特性,就是若是鏈表中的一個組件被釋放的話,它將被自動的從TComponentList 鏈表中刪除。這是利用TComponent的FreeNotification方法能夠在組件被銷燬時通知鏈表,這樣鏈表就能夠將對象引用從鏈表中刪除的。

 

TClassList 類

Contnrs單元中還定義了TClassList類,類定義以下:

TClassList = class(TList) 
protected 
function GetItems(Index: Integer): TClass; 
procedure SetItems(Index: Integer; AClass: TClass); 
public 
function Add(aClass: TClass): Integer; 
function Remove(aClass: TClass): Integer; 
function IndexOf(aClass: TClass): Integer; 
procedure Insert(Index: Integer; aClass: TClass); 
property Items[Index: Integer]: TClass 
read GetItems write SetItems; default; 
end;

不一樣於前面兩個類,這個類繼承於TList的類只是將Add, Remove, IndexOf, Insert和Items 調用的參數從指針換成了TClass元類類型。

 

TOrderedList, TStack和TQueue 類

Contnrs單元還定義了其它三個類:TOrderedList, TStack和TQueue,類型定義以下:

TOrderedList = class(TObject) 
******* 
FList: TList; 
protected 
procedure PushItem(AItem: Pointer); virtual; abstract; 
... 
public 
function Count: Integer; 
function AtLeast(ACount: Integer): Boolean; 
procedure Push(AItem: Pointer); 
function Pop: Pointer; 
function Peek: Pointer; 
end; 
TStack = class(TOrderedList) 
protected 
procedure PushItem(AItem: Pointer); override; 
end; 
TQueue = class(TOrderedList) 
protected 
procedure PushItem(AItem: Pointer); override; 
end;

要注意雖然TOrderedList 並非從TList繼承的,可是它在內部的實現時,使用了TList來儲存指針。另外注意TOrderedList類的PushItem 過程是一個抽象過程,因此咱們沒法實例化 TOrderedList 類,而應該從TOrderedList繼承新的類,並實現抽象的PushItem方法。TStack 和 TQueue 正是實現了PushItem抽象方法的類, 咱們能夠實例化TStack 和TQueue類做爲後進先出的堆棧 (LIFO)和先進先出的隊列(FIFO)。下面是這兩個的的方法使用說明:

·Count 返回列表中的項目數。

·AtLeast 能夠用來檢查鏈表的大小,判斷當前列表中的指針數目是否大於傳遞的參數值,若是爲True表示列表中的項目數大於傳來的參數。

·對於TStack類Push 方法將指針添加到鏈表的最後,對於TQueue類Push 方法則將指針插入到鏈表的開始。

·Pop返回鏈表的末端指針,並將其從鏈表中刪除。

·Peek返回鏈表的末端指針,可是不將其從鏈表中刪除。


TObjectStack和TObjectQueue類

Contnrs單元中最後兩個類是TObjectStack和TObjectQueue類,類的定義以下:

TObjectStack = class(TStack) 
public 
procedure Push(AObject: TObject); 
function Pop: TObject; 
function Peek: TObject; 
end; 
TObjectQueue = class(TQueue) 
public 
procedure Push(AObject: TObject); 
function Pop: TObject; 
function Peek: TObject; 
end;


TIntList 類

這兩個類只是TStack和 TQueue 類的簡單擴展,在鏈表中保存的是TObject的對象引用,而不是簡單的指針。

到目前爲止,咱們看到的容器類中保存的都是指針或者對象引用(對象引用其實也是一種指針)。

那麼咱們能不能在鏈表中保存原生類型,如 Integer,Boolean或者Double等呢。下面的咱們定義的類TIntList 類就能夠在鏈表中保存整數,這裏咱們利用了整數和指針都佔用個字節的存儲空間,因此咱們能夠直接將指針映射爲整數。

unit IntList; 
interface 
uses 
Classes; 
type 
TIntList = class(TList) 
protected 
function GetItem(Index: Integer): Integer; 
procedure SetItem(Index: Integer; 
const Value: Integer); 
public 
function Add(Item: Integer): Integer; 
function Extract(Item: Integer): Integer; 
function First: Integer; 
function IndexOf(Item: Integer): Integer; 
procedure Insert(Index, Item: Integer); 
function Last: Integer; 
function Remove(Item: Integer): Integer; 
procedure Sort; 
property Items[Index: Integer]: Integer 
read GetItem write SetItem; default; 
end; 
implementation 
{ TIntList } 
function TIntList.Add(Item: Integer): Integer; 
begin 
Result := inherited Add(Pointer(Item)); 
end; 
function TIntList.Extract(Item: Integer): Integer; 
begin 
Result := Integer(inherited Extract(Pointer(Item))); 
end; 
function TIntList.First: Integer; 
begin 
Result := Integer(inherited First); 
end; 
function TIntList.GetItem(Index: Integer): Integer; 
begin 
Result := Integer(inherited Items[Index]); 
end; 
function TIntList.IndexOf(Item: Integer): Integer; 
begin 
Result := inherited IndexOf(Pointer(Item)); 
end; 
procedure TIntList.Insert(Index, Item: Integer); 
begin 
inherited Insert(Index, Pointer(Item)); 
end; 
function TIntList.Last: Integer; 
begin 
Result := Integer(inherited Last); 
end; 
function TIntList.Remove(Item: Integer): Integer; 
begin 
Result := inherited Remove(Pointer(Item)); 
end; 
procedure TIntList.SetItem(Index: Integer; 
const Value: Integer); 
begin 
inherited Items[Index] := Pointer(Value); 
end; 
function IntListCompare(Item1, Item2: Pointer): Integer; 
begin 
if Integer(Item1) < Integer(Item2) then 
Result := -1 
else if Integer(Item1) > Integer(Item2) then 
Result := 1 
else 
Result := 0; 
end; 
procedure TIntList.Sort; 
begin 
inherited Sort(IntListCompare); 
end; 
end.

 

擴展TList,限制類型的對象列表

Begin Listing Two - TMyObjectList 
TMyObject = class(TObject) 
public 
procedure DoSomething; 
end; 
TMyObjectList = class(TObjectList) 
protected 
function GetItems(Index: Integer): TMyObject; 
procedure SetItems(Index: Integer; AMyObject: TMyObject); 
public 
function Add(aMyObject: TMyObject): Integer; 
procedure DoSomething; 
function Remove(aMyObject: TMyObject): Integer; 
function IndexOf(aMyObject: TMyObject): Integer; 
procedure Insert(Index: Integer; aMyObject: TMyObject); 
property Items[Index: Integer]: TMyObject 
read GetItems write SetItems; default; 
end; 
... 
{ TMyObjectList } 
function TMyObjectList.Add(AMyObject: TMyObject): Integer; 
begin 
Result := inherited Add(AMyObject); 
end; 
procedure TMyObjectList.DoSomething; 
var 
i: Integer; 
begin 
for i := 0 to Count-1 do 
Items[i].DoSomething; 
end; 
function TMyObjectList.GetItems(Index: Integer): TMyObject; 
begin 
Result := TMyObject(inherited Items[Index]); 
end; 
function TMyObjectList.IndexOf(AMyObject: TMyObject): 
Integer; 
begin 
Result := inherited IndexOf(AMyObject); 
end; 
procedure TMyObjectList.Insert(Index: Integer; 
AMyObject: TMyObject); 
begin 
inherited Insert(Index, AMyObject); 
end; 
function TMyObjectList.Remove(AMyObject: TMyObject): 
Integer; 
begin 
Result := inherited Remove(AMyObject); 
end; 
procedure TMyObjectList.SetItems(Index: Integer; 
AMyObject: TMyObject); 
begin 
inherited Items[Index] := AMyObject; 
end; 
End Listing Two


TStrings類

出於效率的考慮,Delphi並無象C++和Java那樣將字符串定義爲類,所以TList自己不能直接存儲字符串,而字符串列表又是使用很是普遍的,爲此Borland提供了TStrings類做爲存儲字符串的基類,應該說是它除了TList類以外另一個最重要的Delphi容器類。

要注意的是TStrings類自己包含了不少抽象的純虛的方法,所以不能實例化後直接使用,必須從TStrings類繼承一個基類實現全部的抽象的純虛方法來進行實際的字符串列表管理。雖然 TStrings類自己是一個抽象類,可是它應該說是一個使用了Template模式的模版類,提供了不少事先定義好的算法來實現添加添加、刪除列表中的字符串,按下標存取列表中的字符串,對列表中的字符串進行排序,將字符串保存到流中。將每一個字符串同一個對象關聯起來,提供了鍵-值對的關聯等等。

由於TStrings類自己是個抽象類,沒法實例化,所以Delphi提供了一個TStringList的TStrings的子類提供了 TStrings類的默認實現,一般在實際使用中,咱們都應該使用TStringList類存儲字符串列表,代碼示意以下:

var TempList: TStrings; 
begin 
TempList := TStringList.Create; 
try 
TempList.Add(‘字符串1’); 
… 
finally 
TempList.Free; 
end; 
end;

TStrings類的應用很是普遍,不少VCL類的屬性都是TStrings類型,好比TMemo組件的Lines屬性,TListBox的Items屬性等等。下面將介紹一下TStrings類的常見用法。

TStrings類的常見的用法

根據下標存取列表中的字符串是最多見的一種操做,用法示意以下:

StringList1.Strings[0] := '字符串1';

注意在Delphi中,幾乎全部的列表的下標都是以0爲底的,也就是說 Strings[0]是列表中的第一個字符串。另外,因爲Strings屬性是字符串列表類的默認屬性,所以能夠省略Strings,直接用下面的簡便方法存取字符串:

StringList1[0] := '字符串1';

定位一個列表中特定的字符串的位置,可使用IndexOf方法,IndexOf方法將會返回在字符串列表中的第一個匹配的字符串的索引值,若是沒有匹配的字符串則返回-1。好比咱們可使用IndexOf方法來察看特定文件是否存在於文件列表框中,代碼示意以下:

if FileListBox1.Items.IndexOf('TargetFileName') > -1 ...

有一點不方便的是TStrings類沒有提供一個方法能夠查找除了第一個匹配字符串外其餘一樣匹配的字符串的索引,只能是本身遍歷字符串列表來實現,這點不如C++中的模版容器類以及相關的模版算法強大和方便。下面是一個遍歷字符串列表的示意,代碼遍歷列表框中的全部字符串,並將其所有轉化爲大寫的字符串:

procedure TForm1.Button1Click(Sender: TObject);var Index: Integer; 
begin 
for Index := 0 to ListBox1.Items.Count - 1 do 
ListBox1.Items[Index] := UpperCase(ListBox1.Items[Index]); 
end;

前面咱們看到了,要想向字符串列表中添加字符串,直接使用Add方法就能夠了,可是Add方法只能將字符串加入到列表的末尾,要想在列表的指定位置添加字符串,須要使用Insert方法,下面代碼在列表的索引爲2的位置添加了字符串:

StringList1.Insert(2, 'Three');

若是要想將一個字符串列表中的全部字符串都添加到另外一個字符串列表中,可使用 AddStrings方法,用法以下:

StringList1.AddStrings(StringList2);

要想克隆一個字符串列表的全部內容,可使用Assign方法,例以下面的方法將Combox1中的字符串列表複製到了Memo1中:

Memo1.Lines.Assign(ComboBox1.Items);

要注意的是使用了Assign方法後,目標字符串列表中原有的字符串會所有丟失。

同對象關聯

前面說了咱們能夠將字符串同對象綁定起來,咱們可使用AddObject或者InsertObject方法向列表添加同字符串關聯的對象,也能夠經過Objects屬性直接將對象同特定位置的字符串關聯。此外TStrings類還提供了IndexOfObject方法返回指定對象的索引,一樣的Delete,Clear和Move等方法也能夠做用於對象。不過要注意的是咱們不能向字符串中添加一個沒有同字符串關聯的對象。

同視圖交互

剛剛學習使用 Delphi的人都會爲Delphi IDE的強大的界面交互設計功能所震驚,好比咱們在窗體上放上一個ListBox,而後在object Inspector中雙擊它的Items屬性(TStrings類型),在彈出的對話框中,見下圖,咱們輸入一些字符串後,點擊肯定,關閉對話框,就會看到窗體上的ListBox中出現了咱們剛纔輸入的字符串。



能夠咱們在TStrings和默認的實現類TStringList的源代碼中卻找不到同ListBox相關的代碼,那麼這種界面交互是如何作到的呢?

祕密就在於TListBox的Items屬性類型其實是TStrings的基類TListBoxStrings類,咱們看一下這個類的定義:

TListBoxStrings = class(TStrings) 
******* 
ListBox: TCustomListBox; 
protectedpublic 
function Add(const S: string): Integer; override; 
procedure Clear; override; 
procedure Delete(Index: Integer); override; 
procedure Exchange(Index1, Index2: Integer); override; 
function IndexOf(const S: string): Integer; override; 
procedure Insert(Index: Integer; const S: string); override; 
procedure Move(CurIndex, NewIndex: Integer); override; 
end;

能夠看到TListBoxStrings類實現了TStrings類的全部抽象方法,同時在內部有一個ListBox的私有變量。咱們再看一下TListBoxStrings的Add方法:

function TListBoxStrings.Add(const S: string): Integer; 
begin 
Result := -1; 
if ListBox.Style in [lbVirtual, lbVirtualOwnerDraw] then exit; 
Result := SendMessage(ListBox.Handle, LB_ADDSTRING, 0, Longint(PChar(S))); 
if Result < 0 then raise EOutOfResources.Create(SInsertLineError); 
end;

能夠看到TListBoxStrings在內部並無保存添加的字符串,而是直接向Windows的原生列表盒控件發送消息實現的代碼添加,而 Windows的原生列表盒是一個MVC的組件,當內部的數據發生變化時,會自動改變視圖顯示,這就是爲何咱們在設計器中輸入的字符串會馬上顯示在窗體列表框中的緣由了。

因而咱們也就知道爲何Borland將TStrings設計爲一個抽象的類而沒有提供一個默認的存儲方式,就是由於不少的界面組件在內部對數據的存儲有不少不一樣的方式,Borland決定針對不一樣的組件提供不一樣的存儲和交互方式。一樣的咱們要編寫的組件若是有 TStrings類型的屬性,同時也要同界面或者其它資源交互的話,不要使用TStringList來實現,而應該從TStrings派生出新類來實現更好的交互設計。

還有一點要說明的是,Delphi的IDE只在使用Delphi的流機制保存組件到窗體設計文件DFM文件中的時,作了一些特殊的處理,可以自動保存和加載Published的TStrings類型的屬性,下面就是一個ListBox儲存在窗體設計文件DFM中文本形式示意(在窗體設計階段,咱們能夠直接使用View As Text右鍵菜單命令看到下面的文本),咱們能夠注意到在設計時咱們輸入的Items的兩個字符串被保存了起來:

object ListBox1: TListBox 
Left = 64 
Top = 40 
Width = 145 
Height = 73 
ItemHeight = 16 
Items.Strings = ( 
'String1' 
'String2') 
TabOrder = 1 
end

隨後若是運行程序時,VCL庫會使用流從編譯進可執行文件的DFM資源中將Items.Strings列表加載到界面上,這樣就實現了設計是什麼樣,運行時也是什麼樣的所見即所得。

鍵-值對

在實際開發過程當中,咱們常常會碰到相似於字典的定位操做的經過鍵查找相應值的操做,好比經過用戶名查找用戶相應的登錄密碼等。在C++和Java中,標準模版庫和JDK都提供了Map類來實現鍵-值機制,可是Delphi的VCL 庫卻沒有提供這樣的類,可是TStrings類提供了一個簡易的Map替代的實現,那就是Name-Value對。

對於 TStrings來講,所謂的Name-Value對,實際上就是’Key=Value’這樣包含=號的分割的字符串,等號左邊的部分就是Name,等號右邊的部分就是Value。TStrings類提供了IndexOfName和Values等屬性方法來操做Name-Value對。下面是用法示意:

var 
StringList1:TStrings; 
Begin 
StringList1:=TStringList.Create; 
//添加用戶名-密碼對 
StringList1.Add(‘hubdog=aaa’); 
StringList1.Add(‘hubcat=bbb’); 
…. 
//根據用戶名hubdog查找密碼 
Showmessage(StringList1.Values[StringList1.IndexOfName(‘hubdog’)]); 
End;

從Delphi7開始,TStrings類增長了一個NameValueSeparator屬性,咱們能夠經過這個屬性修改默認的Name-Value 分割符號爲=號之外的其它符號了。還要說明的是,TStrings的Name-Value對中的Name能夠不惟一,這有點相似於C++中的 MultiMap,這時經過Values[Names[IndexOfName]]下標操做取到的值不必定是咱們所須要的,另外TStrings類的 Name-Value對的查找定位是採用的遍歷的方式,而不一樣於Java和C++中的Map是基於哈希表或者樹的實現,所以查找和定位的效率很是低,不適用於性能要求很是高的場景。不過從Delphi6開始,VCL庫中在IniFiles單元中提供了一個基於哈希表的字符串列表類 THashedStringList類能夠極大的提升查找定位的速度。

 

THashedStringList類

通常來講,經過鍵來查找值最簡單的辦法是遍歷列表對列表中的鍵進行比較,若是相等則獲取相應的鍵值。可是這種簡單的辦法也是效率最差的一種辦法,當列表中的項目比較少時,這種辦法還能夠接受,可是若是列表中項目很是多的話,這種方法會極大的影響軟件的運行速度。 這時咱們可使用哈希表來快速的經過鍵值來存取列表中的元素。因爲本書並非一本數據結構和算法的書,所以我無心在這裏討論哈希表背後的理論知識,咱們只要知道哈希能夠經過鍵快速定位相應的值就能夠了,對此感興趣的非計算機專業的人能夠去察看相關的書,這裏就不贅述了。

Delphi6中提供的THashedStringList類沒有提供任何的新的方法,只是對IndexOf和IndexOfName函數經過哈希表進行了性能優化,下面這個例子演示了TStringList和THashedStringList之間的性能差別:

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 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多倍。



不過要說明的是,THashedStringList同TStringList類相比,雖然查找的速度大大提升了,可是在添加、刪除字符串後再次進行查找操做時,須要從新計算哈希函數,因此若是頻繁的進行刪除或者添加同查找的複合操做,執行的速度頗有可能比TStringList還要慢,這是使用時須要注意的。

 

TBucketList和TObjectBucketList類

從Delphi6開始,VCL的Contnrs單元中又增長了兩個新的容器類TBucketList和TObjectBucketList。TBucketList實際上也是一個簡單基於哈希表的指針-指針對列表。接口定義以下:

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方法如今接受兩個參數AItem和AData,咱們能夠把它當作是指針版的Map實現(從容器類來看, Delphi從語言的靈活性來講不如C++,爲了實現不一樣類型的哈希Map容器,Delphi須要派生不少的類,而C++的Map是基於模版技術來實現的,容器元素的類型只要簡單的聲明一下就能指定了,使用起來很是方便。而從簡單性來講,則不如Java的容器類,由於Delphi中的String是原生類型,而不是類,而且 Delphi還提供對指針的支持,所以要爲指針和字符串提供不一樣的Map派生類),類中的Exists和Find等方法都是經過哈希表來實現快速數據定位的。同時,同通常的列表容器類不一樣,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 = classpublic 
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函數,它返回第一個不爲True的Boolean值的下標。從接口定義能夠看出,TBits類接口很是簡單,提供的功能也頗有限,我猜想這只是Borland的研發隊伍知足內部開發有限須要的類,並非做爲一個通用類來設計的,好比它沒有開放內部數據存取的接口,沒法得到內部數據的表達,進而沒法實現對狀態的保存和加載等更高的需求。

 

TCollection類

前面咱們提到了 Delphi的IDE可以自動將字符串列表保存在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的數目,同時也提供了Add和Delete方法來添加和刪除TCollectionItem對象以及經過下標存取TCollectionItem的屬性。看上去和容器類區別不大,可是在VCL內部用於保存和加載組件的TReader和TWriter類提供了兩個特殊的方法WriteCollection和 ReadCollection用於加載和保存TCollection類型的集合屬性。IDE就是經過這兩個方法實現對TCollection類型屬性的可持續性。

假設如今須要設計一個火車組件TTrain,TTrain組件有一個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) 
******* 
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) 
******* 
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,GetItem和SetItem都很是簡單,就是調用基類的方法,而後將基類的方法的返回結果從新映射爲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按鈕向列表中添加兩個車箱,修改一下屬性,以下圖所示意:



從上面的屬性編輯器咱們,能夠看到默認狀況下,屬性編輯器列表框是按項目索引加上一個橫槓來顯示車箱的名稱,看起來不是很天然。要想修改顯示字符串,須要重載 TCarriageCollectionItem的GetDisplayName方法。修改後的GetDisplayName方法顯示車箱加車箱號碼:

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

示意圖:



保存一下文件,使用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則是實現所見即所得設計的關鍵類,對於開發靈活強大的自定義組件來講是必不可少的。
相關文章
相關標籤/搜索