常用的hash算法有哪些?
• 加法Hash:把输入元素一个一个的加起来构成最后的结果
• 位运算Hash:这类型Hash函数通过利用各种位运算(常见的是移位和异或)来充分的混合输入元素
• 乘法Hash:这种类型的Hash函数利用了乘法的不相关性(乘法的这种性质,最有名的莫过于平方取头尾的随机数生成算
法,虽然这种算法效果并不好);jdk5.0里面的String类的hashCode()方法也使用乘法Hash;32位FNV算法
• 除法Hash:除法和乘法一样,同样具有表面上看起来的不相关性。不过,因为除法太慢,这种方式几乎找不到真正的应用
• 查表Hash:查表Hash最有名的例子莫过于CRC系列算法。虽然CRC系列算法本身并不是查表,但是,查表是它的一种最快
的实现方式。查表Hash中有名的例子有:Universal Hashing和Zobrist Hashing。他们的表格都是随机生成的。
• 混合Hash:混合Hash算法利用了以上各种方式。各种常见的Hash算法,比如MD5、Tiger都属于这个范围。它们一般很少
在面向查找的Hash函数里面使用
Object类的hashCode.
返回对象的经过处理后的内存地址,由于每个对象的内存地址都不一样,所以哈希码也不一样。这个是native方法,取决于JVM的内部设计,一般是某种C地址的偏移。
String类的hashCode
根据String类包含的字符串的内容,根据 s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] 的计算,返回哈希码,只要字符串的内容相同,返回的哈希码也相同。
public int hashCode() { int h = hash; if (h == 0) { int off = offset; char val[] = value; int len = count; for (int i = 0; i < len; i++) { h = 31*h + val[off++]; } hash = h; } return h; }
为什么选择31作为乘积因子,而且没有用一个常量来声明?主要原因有两个:
①、31是一个不大不小的质数,是作为 hashCode 乘子的优选质数之一。
②、31可以被 JVM 优化,31 * i = (i << 5) - i。因为移位运算比乘法运行更快更省性能。
Integer
返回的哈希码就是Integer对象里所包含的那个整数的数值,例如Integer i1=new Integer(100), i1.hashCode的值就是100 。由此可见,2个一样大小的Integer对象,返回的哈希码也一样。
Long
value ^ (value >>> 32)
Boolean
value ? 1231 : 1237
int,char这样的基本数据类型
它们不需要hashCode,如果需要存储时,将进行自动装箱操作,然后再存储。
hashMap 的 hash():
在hashMap 中,key在数组中的位置计算:需要根据key的 hash值 对其数组长度进行取模得到。
需要注意:元素的 hash 值,不是简单的使用key的 hashCode()。
JDK8 中:
将key的hashCode的二进制表示: 高16位 与 低16位进行异或运算 的得到hash值。
JDK 8 以前:
hashtable
在hashtable中,key在数组中的位置计算:
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
一致性hash
构造一个2^32的整数环,即0~(2^32-1)的数字空间,形成一个环,起点为0,终点为2^32-1。
计算机器的ip的hash值,再对 2^32 取模。即:hash(IP) % 2^32,会映射到圆上的一点。
将存储的key进行hash(key) % 2^32, 它的值在圆上映射的位置开始,顺时针方向找到的第一个机器节点,即为存储该key的机器节点。
如果 新增/删除 服务器,则对受影响的key 进行重新分配即可。
虚拟节点:解决 数据倾斜 的情况,防止出现大量数据存放在某一个 node 的情形。
此时,将真实节点计算多个哈希形成多个虚拟节点并放置到哈希环上,真实节点不放置到哈希环上,只有虚拟节点才会放上去。 每次根据key得到存储对应的某个虚拟节点,还需要做一次虚拟节点向真实节点的映射处理。
hash()方法的选用没有特别指明,但是需要保证出现hash碰撞的可能性要尽可能的小。重新计算Hash值的算法有很多,比如CRC32_HASH、FNV1_32_HASH、KETAMA_HASH等,其中KETAMA_HASH是默认的MemCache推荐的一致性Hash算法,用别的Hash算法也可以,比如FNV1_32_HASH算法的计算效率就会高一些。
/**
* 使用FNV1_32_HASH算法计算服务器的Hash值,这里不使用重写hashCode的方法,最终效果没区别
*/
private static int getHash(String str)
{
final int p = 16777619;
int hash = (int)2166136261L;
for (int i = 0; i < str.length(); i++)
hash = (hash ^ str.charAt(i)) * p;
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;
// 如果算出来的值为负数则取其绝对值
if (hash < 0)
hash = Math.abs(hash);
return hash;
}
hash 槽
Redis 集群的键空间被分割为 16384 个 hash 槽(slot), 集群的最大节点数量也是 16384 个 。
一个 Redis Cluster包含16384(0~16383)个哈希槽,存储在Redis Cluster中的所有键都会被映射到这些slot中。
所有的master节点都会有一个槽区比如:0~1000,槽数是可以迁移的。 master节点的slave节点不分配槽,只拥有读权限。
key的定位规则:根据CRC-16(key)%16384的值来判断属于哪个槽区,从而判断该key属于哪个节点 。CRC16(key) 是用于计算key的 CRC16校验和。
当前集群有3个节点,槽默认是平均分的:
节点 A (6381)包含 0 到 5499号哈希槽.
节点 B (6382)包含5500 到 10999 号哈希槽.
节点 C (6383)包含11000 到 16383号哈希槽.
如果想新添加一个节点D,我需要从节点 A, B, C中得部分槽到D上。
如果想移除节点A,需要将A中的槽,移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可。
由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量,都不会造成集群不可用的状态。
来源:oschina
链接:https://my.oschina.net/langwanghuangshifu/blog/3162706