整数哈希介绍

为什么要整数哈希


  很多时候,可以直接用整数作为键,比如QQ号码,手机号码,但这些号码的分布性不是均匀的(比如高位的变化更少,低位变化更多)。
  分布均匀指的是每位为0或1的概率都是一样的。

理论基础

整数哈希的目标

    1. 函数要是可逆的(1对1的映射)
    2. 雪崩效应(输入中1bit的变化 影响 输出中1/4 到 1/2的bits变化)

可逆操作

    key + const_value 加法是可逆
    key - const_value 减法是可逆
    key ^ const_value 异或是可逆的
    ~key 取反也是可逆的
    key * const_value 乘以一个奇数也是可逆的

复杂操作的可逆性分析

    a = (a+0xfd7046c5) + (a<<3);  // <<和+ 是可逆
    a = (a+0xfd7046c5) + (a>>3);  // >>和+ 不保证是可逆的
    a = (a^0xb55a4f09) ^ (a<<16); // ^ 和<< 是可逆
    a = (a^0xb55a4f09) ^ (a>>16); // ^ 和>> 是可逆

 

雪崩操作

+ bit is 1 导致左边临近的所有1和最后一个0被反转( 0111 + 1 = 1000)
- bit is 1 导致左边临近的所有0和最后一个1被反转( 1000 - 1 = 0111)
所以加法和减法可以从低向高扩散变化

~ 可以把过多的0变1,把过多的1变成零,最后结合原始值,就可以保持0和1的平衡,以便 +或-可以有效扩散变化。

<< 让低位转到高位。
>> 让高位转到低位。

^ 会把某些位反转, 异或可以把两个变量的变化组合一起来,这经常跟移位一起来实现1个bit的变化扩散到2个bit上。

乘法的本质是 多个<< 和 +,也可以引起雪崩。 除法也可以影响雪崩(特别是素数)。
不用乘法和除法的原因的是,整数的乘法和除法太耗CPU了,整数除法是最慢的操作,比浮点数数除法还慢,在一些RISC机器上可能是上百倍的差距。

 

具体哈希算法介绍

乘法

《计算机编程艺术》中讲过: 乘以一个黄金分割数:2654435761(2^32) 可以得到 HASH.
    hash = key*2654435761;
 

乘法的问题在于,低位可以影响高位,但高位不能影响低位。 分布是不均匀的。

 

但不是说乘法hash就没有用,在大量脚本语言中,对象的hash是通过对象的地址值来算hash的(因为对象本身会变化),
对象分配的特点决定了,地址的高位是不变化或变化极少。

uint32 address_hash(char* addr)
{
  register uint32 key;
  key = (uint32) addr;
  return (key >> 3) * 2654435761; //这个3是对齐边界为8
}

这时候MASK, 应该选择高位更好(0xFFFF0000)

 

除法

  

   hash = key%prime


    除法的问题在于, 除数需要是素数,而且整数除法比较耗时。

    如果除数不是素数,就要求hash值是均匀分布。

    其实除法hash多是用在哈希表中求桶的位置,如果不能保证键的哈希函数是均匀的,那么建议使用素数来取模。
     
    如果可以保证键值的哈希函数的质量,那么使用 MASK( &MASK)操作取代取模,效率会更高。

 

其它位运算

Tomas Wang


uint32_t hash32shift(uint32_t key)
{
  key = ~key + (key << 15); // key = (key << 15) - key - 1;
  key = key ^ (key >> 12);
  key = key + (key << 2);
  key = key ^ (key >> 4);
  key = key * 2057; // key = (key + (key << 3)) + (key << 11);
  key = key ^ (key >> 16);
  return key;
}

Bob Jenkins' 32 bit integer hash function


uint32_t hash( uint32_t a)
{
   a = (a+0x7ed55d16) + (a<<12);
   a = (a^0xc761c23c) ^ (a>>19);
   a = (a+0x165667b1) + (a<<5);
   a = (a+0xd3a2646c) ^ (a<<9);
   a = (a+0xfd7046c5) + (a<<3); // <<和 +的组合是可逆的
   a = (a^0xb55a4f09) ^ (a>>16); 
   return a;
}

这六个数是随机数, 通过设置合理的6个数,你可以找到对应的perfect hash.

 

64 bit Mix Functions


uint64_t hash64shift(uint64_t key)
{
  key = (~key) + (key << 21); // key = (key << 21) - key - 1;
  key = key ^ (key >> 24);
  key = (key + (key << 3)) + (key << 8); // key * 265
  key = key ^ (key >> 14);
  key = (key + (key << 2)) + (key << 4); // key * 21
  key = key ^ (key >> 28);
  key = key + (key << 31);
  return key;
}

64 bit to 32 bit Mix Functions


uint32_t hash64_32shift(uint64_t key)
{
  key = (~key) + (key << 18); // key = (key << 18) - key - 1;
  key = key ^ (key >> 31);
  key = key * 21; // key = (key + (key << 2)) + (key << 4);
  key = key ^ (key >> 11);
  key = key + (key << 6);
  key = key ^ (key >> 22);
  return (int) key;
}

Bob Jenkins' 96 bit Mix Function


uint32_t mix(uint32_t a, uint32_t b, uint32_t c)
{
  a=a-b;  a=a-c;  a=a^(c >> 13);
  b=b-c;  b=b-a;  b=b^(a << 8);
  c=c-a;  c=c-b;  c=c^(b >> 13);
  a=a-b;  a=a-c;  a=a^(c >> 12);
  b=b-c;  b=b-a;  b=b^(a << 16);
  c=c-a;  c=c-b;  c=c^(b >> 5);
  a=a-b;  a=a-c;  a=a^(c >> 3);
  b=b-c;  b=b-a;  b=b^(a << 10);
  c=c-a;  c=c-b;  c=c^(b >> 15);
  return c;
}

为什么要整数哈希


  很多时候,可以直接用整数作为键,比如QQ号码,手机号码,但这些号码的分布性不是均匀的(比如高位的变化更少,低位变化更多)。
  分布均匀指的是每位为0或1的概率都是一样的。

理论基础

整数哈希的目标

    1. 函数要是可逆的(1对1的映射)
    2. 雪崩效应(输入中1bit的变化 影响 输出中1/4 到 1/2的bits变化)

可逆操作

    key + const_value 加法是可逆
    key - const_value 减法是可逆
    key ^ const_value 异或是可逆的
    ~key 取反也是可逆的
    key * const_value 乘以一个奇数也是可逆的

复杂操作的可逆性分析

    a = (a+0xfd7046c5) + (a<<3);  // <<和+ 是可逆
    a = (a+0xfd7046c5) + (a>>3);  // >>和+ 不保证是可逆的
    a = (a^0xb55a4f09) ^ (a<<16); // ^ 和<< 是可逆
    a = (a^0xb55a4f09) ^ (a>>16); // ^ 和>> 是可逆

 

雪崩操作

+ bit is 1 导致左边临近的所有1和最后一个0被反转( 0111 + 1 = 1000)
- bit is 1 导致左边临近的所有0和最后一个1被反转( 1000 - 1 = 0111)
所以加法和减法可以从低向高扩散变化

~ 可以把过多的0变1,把过多的1变成零,最后结合原始值,就可以保持0和1的平衡,以便 +或-可以有效扩散变化。

<< 让低位转到高位。
>> 让高位转到低位。

^ 会把某些位反转, 异或可以把两个变量的变化组合一起来,这经常跟移位一起来实现1个bit的变化扩散到2个bit上。

乘法的本质是 多个<< 和 +,也可以引起雪崩。 除法也可以影响雪崩(特别是素数)。
不用乘法和除法的原因的是,整数的乘法和除法太耗CPU了,整数除法是最慢的操作,比浮点数数除法还慢,在一些RISC机器上可能是上百倍的差距。

 

具体哈希算法介绍

乘法

《计算机编程艺术》中讲过: 乘以一个黄金分割数:2654435761(2^32) 可以得到 HASH.
    hash = key*2654435761;
 

乘法的问题在于,低位可以影响高位,但高位不能影响低位。 分布是不均匀的。

 

但不是说乘法hash就没有用,在大量脚本语言中,对象的hash是通过对象的地址值来算hash的(因为对象本身会变化),
对象分配的特点决定了,地址的高位是不变化或变化极少。

uint32 address_hash(char* addr)
{
  register uint32 key;
  key = (uint32) addr;
  return (key >> 3) * 2654435761; //这个3是对齐边界为8
}

这时候MASK, 应该选择高位更好(0xFFFF0000)

 

除法

  

   hash = key%prime


    除法的问题在于, 除数需要是素数,而且整数除法比较耗时。

    如果除数不是素数,就要求hash值是均匀分布。

    其实除法hash多是用在哈希表中求桶的位置,如果不能保证键的哈希函数是均匀的,那么建议使用素数来取模。
     
    如果可以保证键值的哈希函数的质量,那么使用 MASK( &MASK)操作取代取模,效率会更高。

 

其它位运算
Tomas Wang

    uint32_t hash32shift(uint32_t key)
    {
      key = ~key + (key << 15); // key = (key << 15) - key - 1;
      key = key ^ (key >> 12);
      key = key + (key << 2);
      key = key ^ (key >> 4);
      key = key * 2057; // key = (key + (key << 3)) + (key << 11);
      key = key ^ (key >> 16);
      return key;
    }

Bob Jenkins' 32 bit integer hash function

    uint32_t hash( uint32_t a)
    {
       a = (a+0x7ed55d16) + (a<<12);
       a = (a^0xc761c23c) ^ (a>>19);
       a = (a+0x165667b1) + (a<<5);
       a = (a+0xd3a2646c) ^ (a<<9);
       a = (a+0xfd7046c5) + (a<<3); // <<和 +的组合是可逆的
       a = (a^0xb55a4f09) ^ (a>>16); 
       return a;
    }

这六个数是随机数, 通过设置合理的6个数,你可以找到对应的perfect hash.

 
64 bit Mix Functions

    uint64_t hash64shift(uint64_t key)
    {
      key = (~key) + (key << 21); // key = (key << 21) - key - 1;
      key = key ^ (key >> 24);
      key = (key + (key << 3)) + (key << 8); // key * 265
      key = key ^ (key >> 14);
      key = (key + (key << 2)) + (key << 4); // key * 21
      key = key ^ (key >> 28);
      key = key + (key << 31);
      return key;
    }

64 bit to 32 bit Mix Functions

    uint32_t hash64_32shift(uint64_t key)
    {
      key = (~key) + (key << 18); // key = (key << 18) - key - 1;
      key = key ^ (key >> 31);
      key = key * 21; // key = (key + (key << 2)) + (key << 4);
      key = key ^ (key >> 11);
      key = key + (key << 6);
      key = key ^ (key >> 22);
      return (int) key;
    }

Bob Jenkins' 96 bit Mix Function

    uint32_t mix(uint32_t a, uint32_t b, uint32_t c)
    {
      a=a-b;  a=a-c;  a=a^(c >> 13);
      b=b-c;  b=b-a;  b=b^(a << 8);
      c=c-a;  c=c-b;  c=c^(b >> 13);
      a=a-b;  a=a-c;  a=a^(c >> 12);
      b=b-c;  b=b-a;  b=b^(a << 16);
      c=c-a;  c=c-b;  c=c^(b >> 5);
      a=a-b;  a=a-c;  a=a^(c >> 3);
      b=b-c;  b=b-a;  b=b^(a << 10);
      c=c-a;  c=c-b;  c=c^(b >> 15);
      return c;
    }


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值