TStringList的Find,IndexOf和Sort

 转自  http://www.cnblogs.com/monkeyking/articles/234685.html


前几日工作很累,写代码时也有点心猿意马了,看到TStringList.Find便毫不犹豫地使用它在

TStringList内部查找相关的数据。待调试代码时才知道痛苦,浪费无数时间后,只得一步步跟踪,才发

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

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

他用来取得指定行的字符串。分析也就此结束。

-----------------------------------

delphi TStringList 用法详解

//TStringList 常用方法与属性 :
var
  List: TStringList;
  i: Integer;
begin
  List := TStringList.Create;
  List.Add('Strings1');           {添加}
  List.Add('Strings2');
  List.Exchange(0,1);             {置换}
  List.Insert(0,'Strings3');      {插入}
  i := List.IndexOf('Strings1');  {第一次出现的位置}
  List.Sort;                      {排序}
  List.Sorted := True;   {指定排序}
  List.Count;                     {总数}
  List.Text;                      {文本集合}
  List.Delete(0);                 {删除, 0是第一个数据}
  List.LoadFromFile('c:/tmp.txt');{打开}
  List.SaveToFile('c:/tmp.txt');  {保存}
  List.Clear;                     {清空}
  List.Free;                      {释放}
end;

 

 

//读入字符串
var
  List: TStringList;
begin
  List := TStringList.Create;
  List.CommaText := 'aaa,bbb,ccc,ddd';
//相当于: List.Text := 'aaa' + #13#10 + 'bbb' + #13#10' + 'ccc' + '#13#10' + 'ddd';

  ShowMessage(IntToStr(List.Count)); //4
  ShowMessage(List[0]); //aaa

  List.Free;
end;

 

//置换分隔符
var
  List: TStringList;
begin
  List := TStringList.Create;
  List.Delimiter := '|';
  List.DelimitedText := 'aaa|bbb|ccc|ddd';

  ShowMessage(IntToStr(List.Count)); //4
  ShowMessage(List[0]); //aaa

  List.Free;
end;

 

//类似的哈希表操作法
var
  List: TStringList;
begin
  List := TStringList.Create;

  List.Add('aaa=111');
  List.Add('bbb=222');
  List.Add('ccc=333');
  List.Add('ddd=444');

  ShowMessage(List.Names[1]); //bbb
  ShowMessage(List.ValueFromIndex[1]); //222
  ShowMessage(List.Values['bbb']); //222

//ValueFromIndex 可以赋值:
  List.ValueFromIndex[1] := '2';
  ShowMessage(List[1]); //bbb=2

//可以通过 Values 赋值:
  List.Values['bbb'] := '22';
  ShowMessage(List[1]); //bbb=22

  List.Free;
end;

//避免重复值
var
  List: TStringList;
begin
  List := TStringList.Create;

  List.Add('aaa');

  List.Sorted := True; //需要先指定排序
  List.Duplicates := dupIgnore; //如有重复值则放弃

  List.Add('aaa');

  ShowMessage(List.Text); //aaa

//Duplicates 有3个可选值:
//dupIgnore: 放弃;
//dupAccept: 结束;
//dupError: 提示错误.

  List.Free;
end;


//排序与倒排序
{排序函数}
function DescCompareStrings(List: TStringList; Index1, Index2: Integer): Integer;
begin
  Result := -AnsiCompareText(List[Index1], List[Index2]);
end;

procedure TForm 1.Button1Click(Sender: TObject);
var
  List: TStringList;
begin
  List := TStringList.Create;

  List.Add('bbb');
  List.Add('ccc');
  List.Add('aaa');

//未排序
  ShowMessage(List.Text); //bbb ccc aaa

//排序
  List.Sort;
  ShowMessage(List.Text); //aaa bbb ccc

//倒排序
  List.CustomSort(DescCompareStrings); //调用排序函数
  ShowMessage(List.Text); //ccc bbb aaa

//假如:
  List.Sorted := True;
  List.Add('999');
  List.Add('000');
  List.Add('zzz');
  ShowMessage(List.Text); //000 999 aaa bbb ccc zzz
end;
---------------------
作者:sunylat
来源:CSDN
原文:https://blog.csdn.net/sunylat/article/details/24886695
版权声明:本文为博主原创文章,转载请附上博文链接!

,


前几日工作很累,写代码时也有点心猿意马了,看到TStringList.Find便毫不犹豫地使用它在

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;

他用来取得指定行的字符串。分析也就此结束。

-----------------------------------

delphi TStringList 用法详解

//TStringList 常用方法与属性 :
var
  List: TStringList;
  i: Integer;
begin
  List := TStringList.Create;
  List.Add('Strings1');           {添加}
  List.Add('Strings2');
  List.Exchange(0,1);             {置换}
  List.Insert(0,'Strings3');      {插入}
  i := List.IndexOf('Strings1');  {第一次出现的位置}
  List.Sort;                      {排序}
  List.Sorted := True;   {指定排序}
  List.Count;                     {总数}
  List.Text;                      {文本集合}
  List.Delete(0);                 {删除, 0是第一个数据}
  List.LoadFromFile('c:/tmp.txt');{打开}
  List.SaveToFile('c:/tmp.txt');  {保存}
  List.Clear;                     {清空}
  List.Free;                      {释放}
end;

 

 

//读入字符串
var
  List: TStringList;
begin
  List := TStringList.Create;
  List.CommaText := 'aaa,bbb,ccc,ddd';
//相当于: List.Text := 'aaa' + #13#10 + 'bbb' + #13#10' + 'ccc' + '#13#10' + 'ddd';

  ShowMessage(IntToStr(List.Count)); //4
  ShowMessage(List[0]); //aaa

  List.Free;
end;

 

//置换分隔符
var
  List: TStringList;
begin
  List := TStringList.Create;
  List.Delimiter := '|';
  List.DelimitedText := 'aaa|bbb|ccc|ddd';

  ShowMessage(IntToStr(List.Count)); //4
  ShowMessage(List[0]); //aaa

  List.Free;
end;

 

//类似的哈希表操作法
var
  List: TStringList;
begin
  List := TStringList.Create;

  List.Add('aaa=111');
  List.Add('bbb=222');
  List.Add('ccc=333');
  List.Add('ddd=444');

  ShowMessage(List.Names[1]); //bbb
  ShowMessage(List.ValueFromIndex[1]); //222
  ShowMessage(List.Values['bbb']); //222

//ValueFromIndex 可以赋值:
  List.ValueFromIndex[1] := '2';
  ShowMessage(List[1]); //bbb=2

//可以通过 Values 赋值:
  List.Values['bbb'] := '22';
  ShowMessage(List[1]); //bbb=22

  List.Free;
end;

//避免重复值
var
  List: TStringList;
begin
  List := TStringList.Create;

  List.Add('aaa');

  List.Sorted := True; //需要先指定排序
  List.Duplicates := dupIgnore; //如有重复值则放弃

  List.Add('aaa');

  ShowMessage(List.Text); //aaa

//Duplicates 有3个可选值:
//dupIgnore: 放弃;
//dupAccept: 结束;
//dupError: 提示错误.

  List.Free;
end;


//排序与倒排序
{排序函数}
function DescCompareStrings(List: TStringList; Index1, Index2: Integer): Integer;
begin
  Result := -AnsiCompareText(List[Index1], List[Index2]);
end;

procedure TForm 1.Button1Click(Sender: TObject);
var
  List: TStringList;
begin
  List := TStringList.Create;

  List.Add('bbb');
  List.Add('ccc');
  List.Add('aaa');

//未排序
  ShowMessage(List.Text); //bbb ccc aaa

//排序
  List.Sort;
  ShowMessage(List.Text); //aaa bbb ccc

//倒排序
  List.CustomSort(DescCompareStrings); //调用排序函数
  ShowMessage(List.Text); //ccc bbb aaa

//假如:
  List.Sorted := True;
  List.Add('999');
  List.Add('000');
  List.Add('zzz');
  ShowMessage(List.Text); //000 999 aaa bbb ccc zzz
end;
---------------------
作者:sunylat
来源:CSDN
原文:https://blog.csdn.net/sunylat/article/details/24886695
版权声明:本文为博主原创文章,转载请附上博文链接!

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄