我的哈希表 HashMap
这个表是我参照JCL库中的哈希表建的,当前该对象只有 TIntIntHashMap 和 TStrStrHashMap,当然根据数值类型不同可以做其他扩展,实现方法都大同小异。
在网上也有许多封装过数据类型的HashMap,如DGL的泛型库,JCL其实也有一套封装过的HashMap,使用了一段时间后,感觉这些库确实是很强大,但在有些方面也会带来一些不便。
使用感想:是否使用这些泛型库,先要看项目是否需要大量使用到里面的东西。如果你的项目离不开HASH,到处都要用到,那用这些库里的hash表,就比较好了,那些抽象出来的方法确实会节省自己很多时间。而如果一个项目里只有偶尔用到了Hash,那再用强大的泛型库好像就没太大必要了。
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{**************************************************************************************************}
{ WARNING: The Delphi Container Library. Do not edit. }
{**************************************************************************************************}
{**************************************************************************************************}
{ }
{ Last modified: 2008-12-12 13:56:50 }
{ Author: jiangli }
{ }
{**************************************************************************************************}
unit JLHashMap;
interface
uses
SysUtils, Classes;
type
THashFunction = function(Key, Range: Cardinal): Cardinal;
TStrStrEntry = record
Key: string; //AnsiString
Value: string;
end;
TIntIntEntry = record
Key: Integer;
Value: Integer;
end;
TStrIntEntry = record
Key: string;
Value: Integer;
end;
TIntStrEntry = record
Key: Integer;
Value: string;
end;
TStrStrEntryArray = array of TStrStrEntry;
TIntIntEntryArray = array of TIntIntEntry;
TIntStrEntryArray = array of TIntStrEntry;
TStrIntEntryArray = array of TStrIntEntry;
PStrStrBucket = ^TStrStrBucket;
TStrStrBucket = record
Count: Integer; // Count <= Length(Entries); Count是 Entries数组中真正记录有数据的个数。而非 Entries数组的长度。
Entries: TStrStrEntryArray;
end;
PIntIntBucket = ^TIntIntBucket;
TIntIntBucket = record
Count: Integer; // Count <= Length(Entries); Count是 Entries数组中真正记录有数据的个数。而非 Entries数组的长度。
Entries: TIntIntEntryArray;
end;
PStrIntBucket = ^TStrIntBucket;
TStrIntBucket = record
Count: Integer; // Count <= Length(Entries); Count是 Entries数组中真正记录有数据的个数。而非 Entries数组的长度。
Entries: TStrIntEntryArray;
end;
PIntStrBucket = ^TIntStrBucket;
TIntStrBucket = record
Count: Integer; // Count <= Length(Entries); Count是 Entries数组中真正记录有数据的个数。而非 Entries数组的长度。
Entries: TIntStrEntryArray;
end;
TStrStrBucketArray = array of TStrStrBucket;
TIntIntBucketArray = array of TIntIntBucket;
TStrIntBucketArray = array of TStrIntBucket;
TIntStrBucketArray = array of TIntStrBucket;
THashMap = class(TObject)
protected
FHashFunction: THashFunction;
public
property HashFunction: THashFunction read FHashFunction write FHashFunction;
end;
TStrStrHashMap = class(THashMap)
private
FCapacity: Integer; // Length(FBuckets),在Create时被确定,之后不允许改变
FCount: Integer; // 总记录条数,新增一条记录,计数加 1
FBuckets: TStrStrBucketArray; // Hash链表
procedure GrowEntries(BucketIndex: Integer);
public
constructor Create(Capacity: Integer = 16);
destructor Destroy; override;
procedure Clear;
function Size: Integer;
function IsEmpty: Boolean;
function Clone: TStrStrHashMap;
function Equals(AMap: TStrStrHashMap): Boolean;
function ContainsKey(const Key: string): Boolean;
function ContainsValue(const Value: string): Boolean;
function GetValue(const Key: string; ReturnDefaultElements: Boolean = True): string;
function KeyOfValue(const Value: string; ReturnDefaultElements: Boolean = True): string;
function InsertValue(const Key, Value: string): Boolean;
function UpdateValue(const Key, Value: string): Boolean;
function PutValue(const Key, Value: string): Boolean;
function Remove(const Key: string): string;
end;
TIntIntHashMap = class(THashMap)
private
FCapacity: Integer; // Length(FBuckets),在Create时被确定,之后不允许改变
FCount: Integer; // 总记录条数,新增一条记录,计数加 1
FBuckets: TIntIntBucketArray; // Hash链表
procedure GrowEntries(BucketIndex: Integer);
public
constructor Create(Capacity: Integer = 16);
destructor Destroy; override;
procedure Clear;
function Size: Integer;
function IsEmpty: Boolean;
function Clone: TIntIntHashMap;
function Equals(AMap: TIntIntHashMap): Boolean;
function ContainsKey(Key: Integer): Boolean;
function ContainsValue(Value: Integer): Boolean;
function GetValue(Key: Integer; ReturnDefaultElements: Boolean = True): Integer;
function KeyOfValue(Value: Integer; ReturnDefaultElements: Boolean = True): Integer;
function InsertValue(Key, Value: Integer): Boolean;
function UpdateValue(Key, Value: Integer): Boolean;
function PutValue(Key, Value: Integer): Boolean;
function Remove(Key: Integer): Integer;
end;
function HashMul(Key, Range: Cardinal): Cardinal;
implementation
function HashMul(Key, Range: Cardinal): Cardinal;
const
A = 0.6180339887; // (sqrt(5) - 1) / 2
begin
Result := Trunc(Range * (Frac(Key * A)));
end;
//------------------------计算各类型数的Hash---------------------------------//
function HashString(const AValue: string): Cardinal;
var
I: Integer;
begin
Result := 0;
for I := 1 to Length(AValue) do
Inc(Result, Ord(AValue[I]) * (I - 1) * 256); // 区分大小写
//Inc(Result, Ord(UpCase(AValue[I])) * (I - 1) * 256); // 不区分大小写
end;
function HashWideString(const AValue: WideString): Cardinal;
var
I: Integer;
begin
Result := 0;
for I := 1 to Length(AValue) do
Inc(Result, Ord(AValue[I]) * (I - 1) * 65536); // 区分大小写
end;
function HashSingle(const AValue: Single): Cardinal;
const
A = 0.6180339887; // (sqrt(5) - 1) / 2
begin
Result := Round(MaxInt * Frac(AValue * A));
end;
function HashDouble(const AValue: Double): Cardinal;
const
A = 0.6180339887; // (sqrt(5) - 1) / 2
begin
Result := Round(MaxInt * Frac(AValue * A));
end;
function HashExtended(const AValue: Extended): Cardinal;
const
A = 0.6180339887; // (sqrt(5) - 1) / 2
begin
Result := Round(MaxInt * Frac(AValue * A));
end;
function HashInteger(AValue: Integer): Cardinal;
begin
Result := AValue;
end;
function HashCardinal(AValue: Cardinal): Cardinal;
begin
// 与运算,同是1返回1,反之为0:11001100 and 11000011 = 11000000
Result := AValue and MaxInt;
end;
function HashInt64(const AValue: Int64): Cardinal;
begin
// 与运算,同是1返回1,反之为0:11001100 and 11000011 = 11000000
Result := AValue and MaxInt;
end;
function HashPointer(APtr: Pointer): Cardinal;
begin
// 与运算,同是1返回1,反之为0:11001100 and 11000011 = 11000000
Result := Integer(APtr) and MaxInt;
end;
function HashObject(AObject: TObject): Cardinal;
begin
Result := Integer(AObject);
end;
{ TStrStrHashMap }
procedure TStrStrHashMap.Clear;
var
I, J: Integer;
begin
for I := 0 to FCapacity - 1 do
begin
for J := 0 to FBuckets[I].Count - 1 do
begin
FBuckets[I].Entries[J].Key := '';
FBuckets[I].Entries[J].Value := '';
end;
FBuckets[I].Count := 0;
end;
FCount := 0;
end;
function TStrStrHashMap.Clone: TStrStrHashMap;
var
I, J: Integer;
NewEntryArray: TStrStrEntryArray;
NewMap: TStrStrHashMap;
begin
NewMap := TStrStrHashMap.Create(FCapacity);
for I := 0 to FCapacity - 1 do
begin
NewEntryArray := NewMap.FBuckets[I].Entries;
SetLength(NewEntryArray, Length(FBuckets[I].Entries));
for J := 0 to FBuckets[I].Count - 1 do
begin
NewEntryArray[J].Key := FBuckets[I].Entries[J].Key;
NewEntryArray[J].Value := FBuckets[I].Entries[J].Value;
end;
NewMap.FBuckets[I].Count := FBuckets[I].Count;
end;
Result := NewMap;
end;
function TStrStrHashMap.ContainsKey(const Key: string): Boolean;
var
I: Integer;
Bucket: PStrStrBucket;
begin
Result := False;
if Key = '' then
Exit;
Bucket := @FBuckets[FHashFunction(HashString(Key), FCapacity)];
if Bucket <> nil then
for I := 0 to Bucket.Count - 1 do
if Bucket.Entries[I].Key = Key then
begin
Result := True;
Break;
end;
end;
function TStrStrHashMap.ContainsValue(const Value: string): Boolean;
var
I, J: Integer;
Bucket: PStrStrBucket;
begin
Result := False;
if Value = '' then
Exit;
for J := 0 to FCapacity - 1 do
begin
Bucket := @FBuckets[J];
for I := 0 to Bucket.Count - 1 do
if Bucket.Entries[I].Value = Value then
begin
Result := True;
Break;
end;
end;
end;
constructor TStrStrHashMap.Create(Capacity: Integer);
begin
inherited Create;
FCapacity:= Capacity;
SetLength(FBuckets, FCapacity);
FHashFunction:= HashMul;
end;
destructor TStrStrHashMap.Destroy;
begin
Clear;
inherited Destroy;
end;
function TStrStrHashMap.Equals(AMap: TStrStrHashMap): Boolean;
var
I, J: Integer;
begin
Result := False;
if AMap = nil then
Exit;
if FCount <> AMap.Size then
Exit;
Result := True;
for I := 0 to FCapacity - 1 do
for J := 0 to FBuckets[I].Count - 1 do
if AMap.ContainsKey(FBuckets[I].Entries[J].Key) then
begin
if AMap.GetValue(FBuckets[I].Entries[J].Key) <>
FBuckets[I].Entries[J].Value then
begin
Result := False;
Exit;
end;
end
else
begin
Result := False;
Exit;
end;
end;
function TStrStrHashMap.GetValue(const Key: string;
ReturnDefaultElements: Boolean): string;
var
I: Integer;
Bucket: PStrStrBucket;
Found: Boolean;
begin
Result := '';
Found:= False;
if Key <> '' then begin
Bucket := @FBuckets[FHashFunction(HashString(Key),FCapacity)];
if Bucket <> nil then
for I := 0 to Bucket.Count - 1 do
if Bucket.Entries[I].Key = Key then
begin
Result := Bucket.Entries[I].Value;
Found:= True;
Break;
end;
end;
if (not Found) and (not ReturnDefaultElements) then
raise Exception.CreateFmt('Key %s not found', [Key]);
end;
procedure TStrStrHashMap.GrowEntries(BucketIndex: Integer);
var
Capacity: Integer;
begin
Capacity := Length(FBuckets[BucketIndex].Entries);
if Capacity > 64 then Capacity := Capacity + Capacity div 4 else
if Capacity > 8 then Capacity := Capacity + 16 else
Capacity := Capacity + 4;
SetLength(FBuckets[BucketIndex].Entries, Capacity);
end;
// 插入一条新记录,如果记录已经存在,则返回 False
function TStrStrHashMap.InsertValue(const Key, Value: string): Boolean;
var
Index: Integer;
Bucket: PStrStrBucket;
I: Integer;
begin
Result:= False;
if Key = '' then
Exit;
if Value = '' then
Exit;
Index := FHashFunction(HashString(Key), FCapacity);
Bucket := @FBuckets[Index];
if Bucket = nil then
Exit;
for I := 0 to Bucket.Count - 1 do
if Bucket.Entries[I].Key = Key then
Exit;
if Bucket.Count = Length(Bucket.Entries) then
GrowEntries(Index);
Bucket.Entries[Bucket.Count].Key := Key;
Bucket.Entries[Bucket.Count].Value := Value;
Inc(Bucket.Count);
Inc(FCount);
Result:= True;
end;
function TStrStrHashMap.IsEmpty: Boolean;
begin
Result := FCount = 0;
end;
function TStrStrHashMap.KeyOfValue(const Value: string;
ReturnDefaultElements: Boolean): string;
var
I, J: Integer;
Bucket: PStrStrBucket;
Found: Boolean;
begin
Result:= '';
Found:= False;
if Value <> '' then
for J := 0 to FCapacity - 1 do
begin
Bucket := @FBuckets[J];
for I := 0 to Bucket.Count - 1 do
if Bucket.Entries[I].Value = Value then
begin
Result := Bucket.Entries[I].Key;
Found:= True;
Break;
end;
end;
if (not Found) and (not ReturnDefaultElements) then
raise Exception.CreateFmt('Value %s not found', [Value]);
end;
// 插入或更新一条记录,如果记录不存在,则插入。如果记录存在,则更新。
function TStrStrHashMap.PutValue(const Key, Value: string): Boolean;
var
Index: Integer;
Bucket: PStrStrBucket;
I: Integer;
begin
Result:= False;
if Key = '' then
Exit;
if Value = '' then
Exit;
Index := FHashFunction(HashString(Key), FCapacity);
Bucket := @FBuckets[Index];
if Bucket = nil then
Exit;
Result:= True;
for I := 0 to Bucket.Count - 1 do
if Bucket.Entries[I].Key = Key then
begin
Bucket.Entries[I].Value := Value;
Exit;
end;
if Bucket.Count = Length(Bucket.Entries) then
GrowEntries(Index);
Bucket.Entries[Bucket.Count].Key := Key;
Bucket.Entries[Bucket.Count].Value := Value;
Inc(Bucket.Count);
Inc(FCount);
end;
function TStrStrHashMap.Remove(const Key: string): string;
var
Bucket: PStrStrBucket;
I: Integer;
begin
Result := '';
if Key = '' then
Exit;
Bucket := @FBuckets[FHashFunction(HashString(Key), FCapacity)];
if Bucket = nil then
Exit;
for I := 0 to Bucket.Count - 1 do
if Bucket.Entries[I].Key = Key then
begin
Result := Bucket.Entries[I].Value;
if I < Length(Bucket.Entries) - 1 then
System.Move(Bucket.Entries[I + 1], Bucket.Entries[I],
(Bucket.Count - I) * SizeOf(TStrStrEntry));
Dec(Bucket.Count);
Break;
end;
end;
function TStrStrHashMap.Size: Integer;
begin
Result := FCount;
end;
// 更新一条记录的值,如果记录不存在,则返回 False
function TStrStrHashMap.UpdateValue(const Key, Value: string): Boolean;
var
Index: Integer;
Bucket: PStrStrBucket;
I: Integer;
begin
Result:= False;
if Key = '' then
Exit;
if Value = '' then
Exit;
Index := FHashFunction(HashString(Key), FCapacity);
Bucket := @FBuckets[Index];
if Bucket = nil then
Exit;
for I := 0 to Bucket.Count - 1 do
if Bucket.Entries[I].Key = Key then
begin
Bucket.Entries[I].Value := Value;
Result:= True;
Break;
end;
end;
{ TIntIntHashMap }
procedure TIntIntHashMap.Clear;
var
I, J: Integer;
begin
for I := 0 to FCapacity - 1 do
begin
for J := 0 to FBuckets[I].Count - 1 do
begin
FBuckets[I].Entries[J].Key := 0;
FBuckets[I].Entries[J].Value := 0;
end;
FBuckets[I].Count := 0;
end;
FCount := 0;
end;
function TIntIntHashMap.Clone: TIntIntHashMap;
var
I, J: Integer;
NewEntryArray: TIntIntEntryArray;
NewMap: TIntIntHashMap;
begin
NewMap := TIntIntHashMap.Create(FCapacity);
for I := 0 to FCapacity - 1 do
begin
NewEntryArray := NewMap.FBuckets[I].Entries;
SetLength(NewEntryArray, Length(FBuckets[I].Entries));
for J := 0 to FBuckets[I].Count - 1 do
begin
NewEntryArray[J].Key := FBuckets[I].Entries[J].Key;
NewEntryArray[J].Value := FBuckets[I].Entries[J].Value;
end;
NewMap.FBuckets[I].Count := FBuckets[I].Count;
end;
Result := NewMap;
end;
function TIntIntHashMap.ContainsKey(Key: Integer): Boolean;
var
I: Integer;
Bucket: PIntIntBucket;
begin
Result := False;
Bucket := @FBuckets[FHashFunction(HashInteger(Key), FCapacity)];
if Bucket <> nil then
for I := 0 to Bucket.Count - 1 do
if Bucket.Entries[I].Key = Key then
begin
Result := True;
Break;
end;
end;
function TIntIntHashMap.ContainsValue(Value: Integer): Boolean;
var
I, J: Integer;
Bucket: PIntIntBucket;
begin
Result := False;
for J := 0 to FCapacity - 1 do
begin
Bucket := @FBuckets[J];
if Bucket <> nil then
for I := 0 to Bucket.Count - 1 do
if Bucket.Entries[I].Value = Value then
begin
Result := True;
Break;
end;
end;
end;
constructor TIntIntHashMap.Create(Capacity: Integer);
begin
inherited Create;
FCapacity:= Capacity;
SetLength(FBuckets, FCapacity);
FHashFunction:= HashMul;
end;
destructor TIntIntHashMap.Destroy;
begin
Clear;
inherited Destroy;
end;
function TIntIntHashMap.Equals(AMap: TIntIntHashMap): Boolean;
var
I, J: Integer;
begin
Result := False;
if AMap = nil then
Exit;
if FCount <> AMap.Size then
Exit;
Result := True;
for I := 0 to FCapacity - 1 do
for J := 0 to FBuckets[I].Count - 1 do
if AMap.ContainsKey(FBuckets[I].Entries[J].Key) then
begin
if AMap.GetValue(FBuckets[I].Entries[J].Key) <>
FBuckets[I].Entries[J].Value then
begin
Result := False;
Exit;
end;
end
else
begin
Result := False;
Exit;
end;
end;
function TIntIntHashMap.GetValue(Key: Integer;
ReturnDefaultElements: Boolean): Integer;
var
I: Integer;
Bucket: PIntIntBucket;
Found: Boolean;
begin
Result:= 0;
Found:= False;
Bucket := @FBuckets[FHashFunction(HashInteger(Key),FCapacity)];
if Bucket <> nil then
for I := 0 to Bucket.Count - 1 do
if Bucket.Entries[I].Key = Key then
begin
Result := Bucket.Entries[I].Value;
Found:= True;
Break;
end;
if (not Found) and (not ReturnDefaultElements) then
raise Exception.CreateFmt('Key %d not found', [Key]);
end;
procedure TIntIntHashMap.GrowEntries(BucketIndex: Integer);
var
Capacity: Integer;
begin
Capacity := Length(FBuckets[BucketIndex].Entries);
if Capacity > 64 then Capacity := Capacity + Capacity div 4 else
if Capacity > 8 then Capacity := Capacity + 16 else
Capacity := Capacity + 4;
SetLength(FBuckets[BucketIndex].Entries, Capacity);
end;
// 插入一条新记录,如果记录已经存在,则返回 False
function TIntIntHashMap.InsertValue(Key, Value: Integer): Boolean;
var
Index: Integer;
Bucket: PIntIntBucket;
I: Integer;
begin
Result:= False;
Index := FHashFunction(HashInteger(Key), FCapacity);
Bucket := @FBuckets[Index];
if Bucket = nil then
Exit;
for I := 0 to Bucket.Count - 1 do
if Bucket.Entries[I].Key = Key then
Exit;
if Bucket.Count = Length(Bucket.Entries) then
GrowEntries(Index);
Bucket.Entries[Bucket.Count].Key := Key;
Bucket.Entries[Bucket.Count].Value := Value;
Inc(Bucket.Count);
Inc(FCount);
Result:= True;
end;
function TIntIntHashMap.IsEmpty: Boolean;
begin
Result:= FCount = 0;
end;
function TIntIntHashMap.KeyOfValue(Value: Integer;
ReturnDefaultElements: Boolean): Integer;
var
I, J: Integer;
Bucket: PIntIntBucket;
Found: Boolean;
begin
Result:= 0;
Found:= False;
for J := 0 to FCapacity - 1 do
begin
Bucket := @FBuckets[J];
for I := 0 to Bucket.Count - 1 do
if Bucket.Entries[I].Value = Value then
begin
Result := Bucket.Entries[I].Key;
Found:= True;
Break;
end;
end;
if (not Found) and (not ReturnDefaultElements) then
raise Exception.CreateFmt('Value %d not found', [Value]);
end;
// 插入或更新一条记录,如果记录不存在,则插入。如果记录存在,则更新。
function TIntIntHashMap.PutValue(Key, Value: Integer): Boolean;
var
Index: Integer;
Bucket: PIntIntBucket;
I: Integer;
begin
Result:= False;
Index := FHashFunction(HashInteger(Key), FCapacity);
Bucket := @FBuckets[Index];
if Bucket = nil then
Exit;
Result:= True;
for I := 0 to Bucket.Count - 1 do
if Bucket.Entries[I].Key = Key then
begin
Bucket.Entries[I].Value := Value;
Exit;
end;
if Bucket.Count = Length(Bucket.Entries) then
GrowEntries(Index);
Bucket.Entries[Bucket.Count].Key := Key;
Bucket.Entries[Bucket.Count].Value := Value;
Inc(Bucket.Count);
Inc(FCount);
end;
function TIntIntHashMap.Remove(Key: Integer): Integer;
var
Bucket: PIntIntBucket;
I: Integer;
begin
Result := 0;
Bucket := @FBuckets[FHashFunction(HashInteger(Key), FCapacity)];
if Bucket = nil then
Exit;
for I := 0 to Bucket.Count - 1 do
if Bucket.Entries[I].Key = Key then
begin
Result := Bucket.Entries[I].Value;
if I < Length(Bucket.Entries) - 1 then
System.Move(Bucket.Entries[I + 1], Bucket.Entries[I],
(Bucket.Count - I) * SizeOf(TStrStrEntry));
Dec(Bucket.Count);
Break;
end;
end;
function TIntIntHashMap.Size: Integer;
begin
Result:= FCount;
end;
// 更新一条记录的值,如果记录不存在,则返回 False
function TIntIntHashMap.UpdateValue(Key, Value: Integer): Boolean;
var
Index: Integer;
Bucket: PIntIntBucket;
I: Integer;
begin
Result:= False;
Index := FHashFunction(HashInteger(Key), FCapacity);
Bucket := @FBuckets[Index];
if Bucket = nil then
Exit;
for I := 0 to Bucket.Count - 1 do
if Bucket.Entries[I].Key = Key then
begin
Bucket.Entries[I].Value := Value;
Result:= True;
Break;
end;
end;
end.
调用实例:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, JLHashMap, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
imap: TIntIntHashMap;
smap: TStrStrHashMap;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
imap.InsertValue(100,200);
smap.InsertValue('111','222');
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
if imap.ContainsKey(100) then
ShowMessage('存在 KEY 100')
else
ShowMessage('不存在 KEY 100');
if smap.ContainsKey('111') then
ShowMessage('存在 KEY 111')
else
ShowMessage('不存在 KEY 111');
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
imap:= TIntIntHashMap.Create();
smap:= TStrStrHashMap.Create();
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
imap.Free;
smap.Free;
end;
end.