Delphi 词典和排序数据

Delphi Dictionary and ordering data

我的代码是:

procedure TfrmSettings.btnFillDictClick(Sender: TObject);
var
  Dict: TDictionary<string, string>;
  Item: TPair<string, string>;
begin
  Dict := TDictionary<string, string>.Create();

  Dict.Add('Key1', 'Text1');
  Dict.Add('Key2', 'Text2');
  Dict.Add('Key3', 'Text3');
  Dict.Add('Key4', 'Text4');

  for Item in Dict do
  begin
    ShowMessage(Item.Key + ' ' + Item.Value);
  end;    
end;

为什么几乎每次我在 Showmessage 中得到的值都不一样?
为什么值不按添加顺序存储?

我是 Delphi 的菜鸟,不知道 Dictionary 是如何工作的。而且我在 Google 中没有找到任何关于此的信息。 你能解释一下为什么会这样吗?
有什么方法可以在不使用 TList<> 对数据进行排序的情况下使用 Dictionary 吗?

谢谢

字典不维护元素的顺序,因为它的内部组织方式是查找 table 并且它是按键的散列排序的。它们针对速度进行了优化,而不是为了保留顺序。

如果您需要维护元素的顺序,您需要对列表而不是字典。 Delphi 不提供开箱即用的功能。您可以使用以下代码来实现简单的配对列表并根据您的需要对其进行自定义。

type
  TPairs<TKey, TValue> = class(TList < TPair < TKey, TValue >> )
  protected
    fKeyComparer: IComparer<TKey>;
    fValueComparer: IComparer<TValue>;
    function GetValue(Key: TKey): TValue;
    procedure SetValue(Key: TKey; const Value: TValue);
    function ComparePair(const Left, Right: TPair<TKey, TValue>): Integer;
  public
    constructor Create; overload;
    procedure Add(const aKey: TKey; const aValue: TValue); overload;
    function IndexOfKey(const aKey: TKey): Integer;
    function ContainsKey(const aKey: TKey): Boolean; inline;
    property Values[Key: TKey]: TValue read GetValue write SetValue;
  end;

constructor TPairs<TKey, TValue>.Create;
begin
  if fKeyComparer = nil then fKeyComparer := TComparer<TKey>.Default;
  if fValueComparer = nil then fValueComparer := TComparer<TValue>.Default;
  inherited Create(TDelegatedComparer <TPair<TKey, TValue>>.Create(ComparePair));
end;

function TPairs<TKey, TValue>.ComparePair(const Left, Right: TPair<TKey, TValue>): Integer;
begin
  Result := fKeyComparer.Compare(Left.Key, Right.Key);
  if Result = 0 then Result := fValueComparer.Compare(Left.Value, Right.Value);
end;

function TPairs<TKey, TValue>.IndexOfKey(const aKey: TKey): Integer;
var
  i: Integer;
begin
  Result := -1;
  for i := 0 to Count - 1 do
    if fKeyComparer.Compare(Items[i].Key, aKey) = 0 then
      begin
        Result := i;
        break;
      end;
end;

function TPairs<TKey, TValue>.ContainsKey(const aKey: TKey): Boolean;
begin
  Result := IndexOfKey(aKey) >= 0;
end;

function TPairs<TKey, TValue>.GetValue(Key: TKey): TValue;
var
  i: Integer;
begin
  i := IndexOfKey(Key);
  if i >= 0 then Result := Items[i].Value
  else Result := default (TValue);
end;

procedure TPairs<TKey, TValue>.SetValue(Key: TKey; const Value: TValue);
var
  i: Integer;
  Pair: TPair<TKey, TValue>;
begin
  i := IndexOfKey(Key);
  if i >= 0 then FItems[i].Value := Value
  else
    begin
      Pair.Create(Key, Value);
      inherited Add(Pair);
    end;
end;

procedure TPairs<TKey, TValue>.Add(const aKey: TKey; const aValue: TValue);
begin
  SetValue(aKey, aValue);
end;

然后您可以像使用字典一样使用它,但元素的顺序将保持不变。

var
  Pairs: TPairs<string, string>;
  Item: TPair<string, string>;
begin
  Pairs := TPairs<string, string>.Create();

  Pairs.Add('Key1', 'Text1');
  Pairs.Add('Key2', 'Text2');
  Pairs.Add('Key3', 'Text3');
  Pairs.Add('Key4', 'Text4');
  Pairs.Add('Key5', 'Text5');

  for Item in Pairs do
    begin
      Memo1.Lines.Add(Item.Key + ' ' + Item.Value);
    end;
end;

SetValue 更新较新的 Delphi 版本,其中 FItemsTList<T> 后代 类.

中不可用
procedure TPairs<TKey, TValue>.SetValue(Key: TKey; const Value: TValue);
var
  i: Integer;
  Pair: TPair<TKey, TValue>;
begin
  i := IndexOfKey(Key);
  if i >= 0 then
    begin
      Pair := Items[i];
      Pair.Value := Value;
      Items[i] := Pair;
    end
  else
    begin
      Pair.Create(Key, Value);
      inherited Add(Pair);
    end;
end;