Delphi : TStringList的Find,IndexOf和Sort
關鍵:Find要事先Sort排序,Indexof不用排序。
TStringList內部查詢相關的資料。待除錯程式碼時才知道痛苦,浪費無數時間後,只得一步步跟蹤,才發
現Find方法返回的Index總是錯誤的,當時一陣鬱悶,隨手按下F1鍵,Find的Help文件展現眼前,對於該
函式是這樣描述的:
Locates the index for a string in a sorted list and indicates whether a string with that
value already exists in the list.
在Note部分又再次強調:
Only use Find with sorted lists. For unsorted lists, use the IndexOf method instead.
只怪自己一時懶惰,在不瞭解的情況下便拋棄習慣了的IndexOf,輕易使用新函式。但同時我也來了興趣,為什麼Find只能在使用TStringList.Sort方法後才能正常返回資料呢?
老辦法,直接跳到Classes檔案中檢視原始碼:
function TStringList.Find(const S: string; var Index: Integer): Boolean;
var
L, H, I, C: Integer;
begin
Result := False;
L := 0;
H := FCount - 1;
while L <= H do
begin
I := (L + H) shr 1;
C := CompareStrings(FList^[I].FString, S);
if C < 0 then L := I + 1
else begin
H := I - 1;
if C = 0 then
begin
Result := True;
if Duplicates <> dupAccept then L := I;
end;
end;
end;
Index := L;
end;
還是被嚇了一跳,怎麼感覺這麼複雜,仔細一看才明白,原來是個折半查詢演算法。呵呵。
L,H變數分別代表Low和High,(L + H) shr 1就是求中間值的,完全等於(L + H) div 2,對於二進位制,
右移一位就相當於整除2。其中CompareStrings是用來對比兩個字串大小的:
function TStringList.CompareStrings(const S1, S2: string): Integer;
begin
if CaseSensitive then
Result := AnsiCompareStr(S1, S2)
else
Result := AnsiCompareText(S1, S2);
end;
這裡的CaseSensitive用來標記是否大小寫敏感,AnsiCompareStr是大小寫敏感的,AnsiCompareText則
反之。另外在Help文件中還特地說明了兩個函式進行判斷時,小寫字元是小於大寫字元的,比如'a'<'A'
。請注意,這一點是與ASCII不相同的地方(如果再跟下去,你可以發現這兩個函式是對API的一個封裝,
而且封裝了Linux和Windows的兩個版本)。
此時我們返回到Find函式本身,又會發現在判斷條件中只有C<0和C=0的情況,也就是說它只能搜尋升序
排列的StringList。
忍不住,再看了看Sort方法。
procedure TStringList.Sort;
begin
CustomSort(StringListCompareStrings);
end;
簡單的不能再簡單,一行語句。CustomSort是一個公共方法,供使用者使用自定義的比較規則進行排序。
StringListCompareStrings引數中放置的就是自定義比較規則的函式:
TStringListSortCompare = function(List: TStringList; Index1, Index2: Integer): Integer;
CustomSort的程式碼如下:
procedure TStringList.CustomSort(Compare: TStringListSortCompare);
begin
if not Sorted and (FCount > 1) then
begin
Changing;
QuickSort(0, FCount - 1, Compare);
Changed;
end;
end;
Changing和Changed主要是用來觸發FOnChanging和FOnChanged的,具體內容可以自己看程式碼。而
QuickSort則是使用快速排序演算法和使用者自定義的比較規則進行排序了,再跟入到QuickSort程式碼中:
procedure TStringList.QuickSort(L, R: Integer; SCompare: TStringListSortCompare);
var
I, J, P: Integer;
begin
repeat
I := L;
J := R;
P := (L + R) shr 1;
repeat
while SCompare(Self, I, P) < 0 do Inc(I);
while SCompare(Self, J, P) > 0 do Dec(J);
if I <= J then
begin
ExchangeItems(I, J);
if P = I then
P := J
else if P = J then
P := I;
Inc(I);
Dec(J);
end;
until I > J;
if L < J then QuickSort(L, J, SCompare);
L := I;
until I >= R;
end;
哈哈,正是這一段
while SCompare(Self, I, P) < 0 do Inc(I);
while SCompare(Self, J, P) > 0 do Dec(J);
使得TStringList是按照升序排列。至此,大致原因弄明白了。
再看看IndexOf是如何實現搜尋的,剛開始我認為它肯定是使用For迴圈遍歷每個Item,遇到相同的內容
則跳出迴圈,結果發現它確實也是這麼做的,只是中間做了一些優化,假如StringList已經排序過,它
會自動使用效率更高的Find方法進行查詢,另外它使用Result作為迴圈變數,對資源的利用極其充分。
程式碼如下:
function TStringList.IndexOf(const S: string): Integer;
begin
if not Sorted then Result := inherited IndexOf(S) else
if not Find(S, Result) then Result := -1;
end;
其中繼承使用了父類TStrings中的IndexOf方法
function TStrings.IndexOf(const S: string): Integer;
begin
for Result := 0 to GetCount - 1 do
if CompareStrings(Get(Result), S) = 0 then Exit;
Result := -1;
end;
這段程式碼中的Get方法在TStrings中則是純虛擬函式。
function Get(Index: Integer): string; virtual; abstract;
純虛擬函式怎麼能用,倒。那既然能用,只有一個可能,就是子類TStringList中實現了Get方法。返回到
TStringList中,在果然看到以下程式碼:
function TStringList.Get(Index: Integer): string;
begin
if (Index < 0) or (Index >= FCount) then Error(@SListIndexError, Index);
Result := FList^[Index].FString;
end;
他用來取得指定行的字串。分析也就此結束。
》》》》》》》》》》》》》》》》》》》》》》》》》》》》
Find是折半查詢,速度應當是最快了,而indexof預設是 for 輪迴所有item了。 但find應用前必須先排序 sort 不然返回 index錯誤。
示例如下:
var lst:TStringList ;
i:Integer ;
begin
lst:=TStringList.Create ;
try
lst:=TStringList.Create ;
lst.CaseSensitive :=true;
lst.Delimiter :="","";
lst.DelimitedText :=Edit1.Text ;
ShowMessage(IntToStr(lst.IndexOf(Edit2.Text) ));
lst.Sort ;
if lst.Find(Edit2.Text ,i) then
ShowMessage(IntToStr(i));
finally
lst.Free ;
end;
eidt2 內容 如下字串 010a,010A,200a,200b,905a