如何改进代码(Delphi)在字典中加载和搜索?

时间:2011-03-13 07:31:27

标签: delphi hash

我是德尔福程序员。 我创建了一个程序,它使用带有单词和表达式的字典(在程序中加载为“字符串数组”)。 它使用基于“校验和”的搜索算法(我希望这是正确的单词)。 字符串基于以下内容转换为整数:

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;

3 个答案:

答案 0 :(得分:4)

不要reinvent the wheel

恕我直言,你的哈希远没有效率,你的碰撞算法可以改进。

查看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库可用。