整数哈希

整数哈希介绍

为什么要整数哈希

很多时候,可以直接用整数作为键,比如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;
}

参考

http://www.concentric.net/~ttwang/tech/inthash.htm

时间: 2024-11-05 13:48:34

整数哈希的相关文章

哈希算法原理

一.概念 哈希表就是一种以 键-值(key-indexed) 存储数据的结构,我们只要输入待查找的值即key,即可查找到其对应的值. 哈希的思路很简单,如果所有的键都是整数,那么就可以使用一个简单的无序数组来实现:将键作为索引,值即为其对应的值,这样就可以快速访问任意键的值.这是对于简单的键的情况,我们将其扩展到可以处理更加复杂的类型的键. 使用哈希查找有两个步骤: 1. 使用哈希函数将被查找的键转换为数组的索引.在理想的情况下,不同的键会被转换为不同的索引值,但是在有些情况下我们需要处理多个键

Map容器——HashMap及常用API,及put,get方法解析,哈希码的产生和使用

Map接口 ①   映射(map)是一个存储键/值对的对象.给定一个键,可以查询到它的值,键和值都是对象; ②   键必须是唯一的,值可以重复; ③   有些映射可以接收null键和null值,而有的不行; ④   下面的接口可以支持映射: 接口 描述 Map 映射唯一关键字给值 Map.Entry 描述映射中的元素(关键字/值对).这是Map的一个内部类 SortedMap 扩展Map以便关键字按升序保持 ⑤   Map接口映射唯一键到值; ⑥   键(key)是以后用于检索值的对象.给定一个

【算法】哈希表的诞生(Java)

参考资料 <算法(java)>                           — — Robert Sedgewick, Kevin Wayne <数据结构>                                  — — 严蔚敏 为什么要使用哈希表 查找和插入是查找表的两项基本操作,对于单纯使用链表,数组,或二叉树实现的查找表来说,这两项操作在时间消耗上仍显得比较昂贵. 以查找为例:在数组实现的查找表中,需要用二分等查找方式进行一系列的比较后,才能找到给定的键值对

memcached全面剖析--4

memcached的分布式算法   memcached的分布式 正如第1次中介绍的那样, memcached虽然称为“分布式”缓存服务器,但服务器端并没有“分布式”功能. 服务器端仅包括 第2次. 第3次 前坂介绍的内存存储功能,其实现非常简单. 至于memcached的分布式,则是完全由客户端程序库实现的. 这种分布式是memcached的最大特点. memcached的分布式是什么意思? 这里多次使用了“分布式”这个词,但并未做详细解释. 现在开始简单地介绍一下其原理,各个客户端的实现基本相

关于大型站点技术演进的思考(四)--存储的瓶颈(4)

假设数据库须要进行水平拆分,这事实上是一件非常开心的事情,由于它代表公司的业务正在迅猛的增长,对于开发者而言那就是有不尽的项目能够做,尽管会感觉非常忙.可是人过的充实,心里也踏实. 数据库水平拆分简单说来就是先将原数据库里的一张表在做垂直拆分出来放置在单独的数据库和单独的表里后更进一步的把本来是一个总体的表进一步拆分成多张表,每一张表都用独立的数据库进行存储.当表被水平拆分后,原数据表成为了一个逻辑的概念,而这个逻辑表的业务含义须要多张物理表协同完毕.因此数据库的表被水平拆分后.那么我们对这张表

memcached全面剖析–4. memcached的分布式算法

系列文章导航: memcached完全剖析–1. memcached的基础 memcached全面剖析–2. 理解memcached的内存存储 memcached全面剖析–3. memcached的删除机制和发展方向 memcached全面剖析–4. memcached的分布式算法 memcached全面剖析–5. memcached的应用和兼容程序 发表日:2008/7/23 作者:长野雅广(Masahiro Nagano) 原文链接:http://gihyo.jp/dev/feature/0

Hash的应用

重新整理了一下Hash表的应用: 首先,常用的整数哈希: 取模法 取乘法 取模顾名思义就是%p为hash值: 1 #define hash(i) (i%p) 取模顾名思义就是*p取一部分(此处用自然溢出)为hash值: 1 #define hash(i) ((unsigned int)(i*p)>>p_) 接着,字符串取模: 1 #define hash(i,j) mo(f[j]-f[i-1]*fp[j-i+1],Q) 2 for (int i=1;i<=n;i++) f[i]=mo(f

Memcache技术分享:介绍、使用、存储、算法、优化、命中率

原文地址:http://zhihuzeye.com/archives/2361 1.memcached 介绍 1.1 memcached 是什么? memcached 是以LiveJournal旗下Danga Interactive 公司的Brad Fitzpatric 为首开发的一款软件.现在已成为mixi.hatena.Facebook.Vox.LiveJournal 等众多服务中提高Web应用扩展性的重要因素.许多Web 应用都将数据保存到RDBMS 中,应用服务器从中读取数据并在浏览器中

Memcached之分布式算法

Memcached之分布式算法 memcached的分布式: memcached虽然称为“分布式”缓存服务器,但服务器端并没有“分布式”功能.memcached的分布式,则是完全由客户端程序库实现的.这种分布式是memcached的最大特点. Memcached分布式原理: 下面假设memcached服务器有node1-node3三台,应用程序要保存键名为“tokyo”.“kanagawa”.“chiba”.“saitama”.“gunma”的数据. 图4.1:分布式简介:准备 首先向memca