简单的字符串散列函数

时间:2010-09-11 10:26:09

标签: string delphi hash

我正在尝试将字符串哈希为一个整数,以便将其放入数组中。但是我不太了解散列函数,这就是为什么我现在的方法只是将字符的所有ASCII数字加在一起并将其调整为数组大小。

有没有简单快速/更好的方法?

6 个答案:

答案 0 :(得分:12)

FNV-1a hash快速且易于实施。

答案 1 :(得分:4)

有关非常好的散列函数小组,请参阅http://www.strchr.com/hash_functions

在Delphi实现中,这里有几个版本:

首先想到的是官方TStringHash.HashOf单元IniFiles.pas方法中使用的方法。包括更快的asm版本:

function HashOf(P: PByteArray; Len: integer): cardinal;
// algorithm from IniFiles.TStringHash.HashOf
{$ifdef PUREPASCAL}
var I: Integer;
begin
  Result := 0;
  for I := 1 to Len do
    Result := ((Result shl 2) or (Result shr (SizeOf(Result)*8-2))) xor P[I];
end;
{$else}
asm // faster asm version by Synopse
    or edx,edx
    jz @z
    push ebx
    mov ebx,edx     // ebx = length(Key)
    mov edx,eax     // edx = Text
    xor eax,eax     // eax = Result
    xor ecx,ecx     // ecx = Result shl 2 = 0
@1: shr eax,$1e     // eax = Result shr (SizeOf(Result) * 8 - 2))
    or ecx,eax      // ecx = ((Result shl 2) or (Result shr (SizeOf(Result)*8-2)))
    movzx eax,byte ptr [edx] // eax = ord(Key[i])
    inc edx
    xor eax,ecx     // eax = () xor ord(Key[i])
    dec ebx
    lea ecx,[eax*4] // ecx = Result shl 2
    jnz @1
    pop ebx
@z:
end;
{$endif}

经典的Kernighan&来自“The C programming Language”的Ritchie hash,第3版 - 不是最好的,而是简单而有效的代码。

function kr32(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal;
var i: integer;
begin
  for i := 0 to len-1 do
    crc := ord(buf[i])+crc*31;
  result := crc;
end;

zlib中实现的快速“Adler”CRC - 优化的asm版本here

function Adler32Pas(Adler: cardinal; p: pointer; Count: Integer): cardinal;
var s1, s2: cardinal;
    i, n: integer;
begin
  s1 := LongRec(Adler).Lo;
  s2 := LongRec(Adler).Hi;
  while Count>0 do begin
    if Count<5552 then
      n := Count else
      n := 5552;
    for i := 1 to n do begin
      inc(s1,pByte(p)^);
      inc(cardinal(p));
      inc(s2,s1);
    end;
    s1 := s1 mod 65521;
    s2 := s2 mod 65521;
    dec(Count,n);
  end;
  result := word(s1)+cardinal(word(s2)) shl 16;
end;

我自己更快的变体 - 不是重入的,但更快,因为它会被DWORD读取 - 而且速度更快asm version here

function Hash32(Data: pointer; Len: integer): cardinal;
function SubHash(P: PCardinalArray; L: integer): cardinal;
{$ifdef HASINLINE}inline;{$endif}
var s1,s2: cardinal;
    i: PtrInt;
const Mask: array[0..3] of cardinal = (0,$ff,$ffff,$ffffff);
begin
  if P<>nil then begin
    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(Data,Len);
end;

经典的CRC32版本 - 你可以找到一个非常optimized asm version (using 8 tables) here

function UpdateCrc32(aCRC32: cardinal; inBuf: pointer; inLen: integer) : cardinal;
var i: integer;
begin
  result := aCRC32;
  // if we used a dynamic table, we assume we want shorter code size
  for i := 1 to inLen do begin
    result := crc32Tab[byte(result xor pByte(inBuf)^)] xor (result shr 8);
    inc(cardinal(inBuf));
  end;
end;

答案 2 :(得分:3)

正如Dummy00001指出的那样,之前已经被问到并回答过。看看Best algorithm for hashing number values?,特别是使用MurmurHash的建议。

我推荐MurmurHash,因为:

  1. 速度非常快。

  2. 它的分布和雪崩特性非常适合非加密哈希。

  3. 其最坏情况的行为仍然相当不错。

  4. 我用过它。它并不糟糕。

    修改

    https://forums.embarcadero.com/thread.jspa?threadID=13902&tstart=0上有很多关于如何最好地将它移植到Delphi的讨论。生成的代码位于https://forums.codegear.com/thread.jspa?threadID=14879

    德尔福翻译

    function Murmur2(const S: AnsiString; const Seed: LongWord=$9747b28c): LongWord;
    var
        h: LongWord;
        len: LongWord;
        k: LongWord;
        data: Integer;
    const
        // 'm' and 'r' are mixing constants generated offline.
        // They're not really 'magic', they just happen to work well.
        m = $5bd1e995;
        r = 24;
    begin
        len := Length(S);
    
        //The default seed, $9747b28c, is from the original C library
    
        // Initialize the hash to a 'random' value
        h := seed xor len;
    
        // Mix 4 bytes at a time into the hash
        data := 1;
    
        while(len >= 4) do
        begin
            k := PLongWord(@S[data])^;
    
            k := k*m;
            k := k xor (k shr r);
            k := k* m;
    
            h := h*m;
            h := h xor k;
    
            data := data+4;
            len := len-4;
        end;
    
        {   Handle the last few bytes of the input array
                S: ... $69 $18 $2f
        }
        Assert(len <= 3);
        if len = 3 then
            h := h xor (LongWord(s[data+2]) shl 16);
        if len >= 2 then
            h := h xor (LongWord(s[data+1]) shl 8);
        if len >= 1 then
        begin
            h := h xor (LongWord(s[data]));
            h := h * m;
        end;
    
        // Do a few final mixes of the hash to ensure the last few
        // bytes are well-incorporated.
        h := h xor (h shr 13);
        h := h * m;
        h := h xor (h shr 15);
    
        Result := h;
    end;
    

    通过所有self-tests from the original C implementation

答案 3 :(得分:2)

Jenkins hash function应该可以帮助您入门。

  

我当前的方法只是将字符的所有ASCII数字加在一起,并将其修改为数组大小。

您丢弃重要的信息位,即字符串中字符的位置。这是个坏主意,因为字符串“AB”和“BA”将具有相同的哈希值。

不是简单地添加,保持原始,而是可以使用像hash = hash*P1 + str[i]*P2 + P3;这样的表达式,其中P i 是一些素数。如果我需要快速的哈希函数,我就是这样做的。我经常使用7,5和3作为素数,但应明显调整数字(以及hash的初始值),以便哈希函数的结果可用于您的任务。

有关详细信息,请参阅the corresponding (and rather informative) Wikipedia article

答案 4 :(得分:1)

我尝试过很多快速哈希函数并选择了这个函数:

function StrHash(const st:string):cardinal; 
 var
  i:integer;
 begin
  result:=0;
  for i:=1 to length(st) do
   result:=result*$20844 xor byte(st[i]);
 end;

它与K&amp; R功能一样快(实际上甚至更快)但更好(更均匀)分布。

答案 5 :(得分:-3)

一种非常简单的方法是对所有值进行异或。据我所知,这是最简单的。