我是德尔福程序员。 我创建了一个程序,它使用带有单词和表达式的字典(在程序中加载为“字符串数组”)。 它使用基于“校验和”的搜索算法(我希望这是正确的单词)。 字符串基于以下内容转换为整数:
var
FHashSize: Integer; //stores the value of GetHashSize
HashTable, HashTableNoCase: array[Byte] of Longword;
HashTableInit: Boolean = False;
const
AnsiLowCaseLookup: array[AnsiChar] of AnsiChar = (
#$00, #$01, #$02, #$03, #$04, #$05, #$06, #$07,
#$08, #$09, #$0A, #$0B, #$0C, #$0D, #$0E, #$0F,
#$10, #$11, #$12, #$13, #$14, #$15, #$16, #$17,
#$18, #$19, #$1A, #$1B, #$1C, #$1D, #$1E, #$1F,
#$20, #$21, #$22, #$23, #$24, #$25, #$26, #$27,
#$28, #$29, #$2A, #$2B, #$2C, #$2D, #$2E, #$2F,
#$30, #$31, #$32, #$33, #$34, #$35, #$36, #$37,
#$38, #$39, #$3A, #$3B, #$3C, #$3D, #$3E, #$3F,
#$40, #$61, #$62, #$63, #$64, #$65, #$66, #$67,
#$68, #$69, #$6A, #$6B, #$6C, #$6D, #$6E, #$6F,
#$70, #$71, #$72, #$73, #$74, #$75, #$76, #$77,
#$78, #$79, #$7A, #$5B, #$5C, #$5D, #$5E, #$5F,
#$60, #$61, #$62, #$63, #$64, #$65, #$66, #$67,
#$68, #$69, #$6A, #$6B, #$6C, #$6D, #$6E, #$6F,
#$70, #$71, #$72, #$73, #$74, #$75, #$76, #$77,
#$78, #$79, #$7A, #$7B, #$7C, #$7D, #$7E, #$7F,
#$80, #$81, #$82, #$83, #$84, #$85, #$86, #$87,
#$88, #$89, #$8A, #$8B, #$8C, #$8D, #$8E, #$8F,
#$90, #$91, #$92, #$93, #$94, #$95, #$96, #$97,
#$98, #$99, #$9A, #$9B, #$9C, #$9D, #$9E, #$9F,
#$A0, #$A1, #$A2, #$A3, #$A4, #$A5, #$A6, #$A7,
#$A8, #$A9, #$AA, #$AB, #$AC, #$AD, #$AE, #$AF,
#$B0, #$B1, #$B2, #$B3, #$B4, #$B5, #$B6, #$B7,
#$B8, #$B9, #$BA, #$BB, #$BC, #$BD, #$BE, #$BF,
#$C0, #$C1, #$C2, #$C3, #$C4, #$C5, #$C6, #$C7,
#$C8, #$C9, #$CA, #$CB, #$CC, #$CD, #$CE, #$CF,
#$D0, #$D1, #$D2, #$D3, #$D4, #$D5, #$D6, #$D7,
#$D8, #$D9, #$DA, #$DB, #$DC, #$DD, #$DE, #$DF,
#$E0, #$E1, #$E2, #$E3, #$E4, #$E5, #$E6, #$E7,
#$E8, #$E9, #$EA, #$EB, #$EC, #$ED, #$EE, #$EF,
#$F0, #$F1, #$F2, #$F3, #$F4, #$F5, #$F6, #$F7,
#$F8, #$F9, #$FA, #$FB, #$FC, #$FD, #$FE, #$FF);
implementation
function GetHashSize(const Count: Integer): Integer;
begin
if Count < 65 then
Result := 256
else
Result := Round(IntPower(16, Ceil(Log10(Count div 4) / Log10(16))));
end;
function Hash(const Hash: LongWord; const Buf; const BufSize: Integer): LongWord;
var P: PByte;
I: Integer;
begin
P := @Buf;
Result := Hash;
for I := 1 to BufSize do
begin
Result := HashTable[Byte(Result) xor P^] xor (Result shr 8);
Inc(P);
end;
end;
function HashStrBuf(const StrBuf: Pointer; const StrLength: Integer; const Slots: LongWord): LongWord;
var P: PChar;
I, J: Integer;
begin
if not HashTableInit then
InitHashTable;
P := StrBuf;
if StrLength <= 48 then // Hash all characters for short strings
Result := Hash($FFFFFFFF, P^, StrLength)
else
begin
// Hash first 16 bytes
Result := Hash($FFFFFFFF, P^, 16);
// Hash last 16 bytes
Inc(P, StrLength - 16);
Result := Hash(Result, P^, 16);
// Hash 16 bytes sampled from rest of string
I := (StrLength - 48) div 16;
P := StrBuf;
Inc(P, 16);
for J := 1 to 16 do
begin
Result := HashTable[Byte(Result) xor Byte(P^)] xor (Result shr 8);
Inc(P, I + 1);
end;
end;
// Mod into slots
if Slots <> 0 then
Result := Result mod Slots;
end;
procedure InitHashTable;
var I, J: Byte;
R: LongWord;
begin
for I := $00 to $FF do
begin
R := I;
for J := 8 downto 1 do
if R and 1 <> 0 then
R := (R shr 1) xor $EDB88320
else
R := R shr 1;
HashTable[I] := R;
end;
Move(HashTable, HashTableNoCase, Sizeof(HashTable));
for I := Ord('A') to Ord('Z') do
HashTableNoCase[I] := HashTableNoCase[I or 32];
HashTableInit := True;
end;
HashStrBuf的结果是“和(FHashSize - 1)”并用作“整数数组数组”(FHashSize大小)中的索引,用于存储来自该“字符串数组”的字符串的索引。 这样,当搜索字符串时,它会在“校验和”中进行转换,然后代码在“分支”中搜索,并使用此索引将此字符串与具有相同“校验和”的字典中的字符串进行比较。
理想情况下,字典中的每个字符串都应具有唯一的校验和。但在“现实世界”中,大约2/3与其他词语共享相同的“校验和”。因此,搜索速度并不快。 在这些字典中,字符串由这些字符组成:['a'..'z',#224 ..#246,#248 ..#254,#154,#156 ..#159,#179,#186, #191,#190,#185,'0'..'9',''''] 有没有办法改善“哈希”,所以字符串会有更多独特的“校验和”? 哦,一种方法是增加“整数数组”(FHashSize)的大小,但它不能增加太多,因为它需要很多Ram。
另一件事:这些词典仅作为单词/表达式存储在HDD上(不是“校验和”)。他们的“校验和”是在程序启动时生成的。但要做到这一点需要很长时间...... 有没有办法加快程序的启动?也许通过改进“散列”功能,可能将“校验和”存储在HDD上并从那里加载......
任何意见都会受到赞赏......
PS:这是要搜索的代码:
function TDictionary.LocateKey(const Key: AnsiString): Integer;
var i, j, l, H: Integer;
P, Q: PChar;
begin
Result := -1;
l := Length(Key);
H := HashStrBuf(@Key[1], l, 0) and (FHashSize - 1);
P := @Key[1];
for i := 0 to High(FHash[H]) do //FHash is that "array of array of integer"
begin
if l <> FKeys.ItemSize[FHash[H][i]] then //FKeys.ItemSize is an byte array with the lengths of strings from dictionary
Continue;
Q := FKeys.Pointer(FHash[H][i]); //pointer to string in dictionary
for j := 0 to l - 1 do
if (P + j)^ <> (Q + j)^ then
Break;
if j = l then
begin
Result := FHash[H][i];
Exit;
end;
end;
end;
答案 0 :(得分:4)
恕我直言,你的哈希远没有效率,你的碰撞算法可以改进。
查看IniFiles单元和THashedStringList的实例。 它有点旧,但对于使用哈希的字符串列表来说是一个很好的开始。
有很多很好的Delphi实现,比如SuperObject和很多other code ......
查看我们的SynBigTable单元,它可以使用全索引搜索快速处理内存或文件中的数据数组。或围绕任何动态数据数组our latest TDynArray wrapper,以实现类似TList的方法,包括快速二进制搜索。我很确定它可能比使用散列的手动调整代码更快,如果你使用有序索引然后快速二进制搜索。
发表-Scriptum:强>
关于字符串内容的纯散列速度,看一下这个函数 - 将RawByteString重命名为AnsiString,将PPtrInt重命名为PPointer,将PtrInt重命名为Delger 7的整数:
function Hash32(const Text: RawByteString): cardinal;
function SubHash(P: PCardinalArray): cardinal;
{$ifdef HASINLINE}inline;{$endif}
var s1,s2: cardinal;
i, L: PtrInt;
const Mask: array[0..3] of cardinal = (0,$ff,$ffff,$ffffff);
begin
if P<>nil then begin
L := PPtrInt(PtrInt(P)-4)^; // fast lenght(Text)
s1 := 0;
s2 := 0;
for i := 1 to L shr 4 do begin // 16 bytes (4 DWORD) by loop - aligned read
inc(s1,P^[0]);
inc(s2,s1);
inc(s1,P^[1]);
inc(s2,s1);
inc(s1,P^[2]);
inc(s2,s1);
inc(s1,P^[3]);
inc(s2,s1);
inc(PtrUInt(P),16);
end;
for i := 1 to (L shr 2)and 3 do begin // 4 bytes (DWORD) by loop
inc(s1,P^[0]);
inc(s2,s1);
inc(PtrUInt(P),4);
end;
inc(s1,P^[0] and Mask[L and 3]); // remaining 0..3 bytes
inc(s2,s1);
result := s1 xor (s2 shl 16);
end else
result := 0;
end;
begin // use a sub function for better code generation under Delphi
result := SubHash(pointer(Text));
end;
在SynCommons.pas单元中甚至还有一个纯粹的asm版本,甚至更快。我不知道任何更快的哈希函数(它比crc32 / adler32 / IniFiles.hash ...更快)。它基于adler32,但使用DWORD对齐读取和求和可获得更好的速度。当然,这可以通过SSE asm进行改进,但这里是一个快速纯Delphi散列函数。
然后不要忘记使用“乘法”/“二进制和操作”进行散列解析,就像在IniFiles中一样。它会减少哈希列表的迭代次数。
但是由于你没有提供搜索源代码,我们无法知道这里可以改进的内容。
答案 1 :(得分:3)
如果您使用的是Delphi 7,请考虑使用Julian Bucknall可爱的Delphi数据类型代码EzDsl(Easy Data Structures Library)。
现在你不必像另一位聪明人所说的那样重新发明轮子。
您可以下载ezdsl,这是我使用Delphi 7和最近的unicode delphi版本here的版本。
特别是单元名称EHash包含一个哈希表实现,它具有各种可插入的哈希算法,或者您可以编写自己的插件函数,只执行您选择的哈希函数。
如果你使用的是Unicode Delphi版本,那就明智了;我会小心使用像这样的代码库来散列你的unicode字符串,而不检查它的散列算法在你的系统上的执行情况。这里的OP使用的是Delphi 7,因此Unicode不是原始问题的一个因素。
答案 2 :(得分:1)
我认为你会发现一个数据库(没有校验和)更快。也许尝试sqlite,它将为您提供单个文件数据库。有许多Delphi库可用。