计算字符串"code"的哈希值
ASCII值 A = 65 ---Z = 90, a = 97 --- z = 122
c = 99 o = 111 d = 100 e =101
参考计算公式:
s [0] * 31 ^(n-1)+ s [1] * 31 ^(n-2)+ ... + s [n-1]
得99 * 31^3 + 111 * 31^2 + 100 * 31 ^1 + 101 = 3059181
// 代码计算同上列手算
String str = "code";
System.out.println(str.hashCode()); // 3059181
// c = 99, o = 111, d = 100, e = 101
System.out.println('c'*31*31*31 + 'o'*31*31 +'d'*31 + 'e'); // 3059181
为什么是素数不是偶数
假若乘数为10,那么以0结尾的整数,经过散列函数计算获得的哈希值可能会聚集在一起,产生冲突,还需要经过再散列或其他方法来解决冲突。
在解决冲突的过程中,探针会索引没有利用的存储空间,来保存当前对象,因此降低了存储效率
为什么不可以是小一点的素数 2/3 呢
同样是因为无法有效的进行散列,得到的hash值会聚集在一起
为什么不可以是101甚至更大的素数 233
假如乘数因子是,使用上述散列函数计算有 99*101^3 = 101,999,799 只是计算四位字母的第一个字母,hash值就已经达到一亿了。
int整型的范围为-2,147,483,648~2,147,483,647。如果是较长的字符串,其计算结果会在整型范围内溢出,最终会导致数值信息丢失
因此只能选择不大不小的乘数因子
故选择 31, 33, 37, 39 和 41作为乘数,每个常数算出的哈希值冲突数都非常小。
如果对超过 50,000 个英文单词(由两个不同版本的 Unix 字典合并而成)进行 hash code 运算,并使用常数 31, 33, 37, 39 和 41 作为乘子,每个常数算出的哈希值冲突数都小于7个
为什么选择31作为乘数因子
JVM里最有效的计算方式就是进行位运算
而数字31有一个很好的特性,即乘法运算可以被移位和减法运算取代,来获取更好的性能
即 31 * i == (i << 5) - i
左移 << : 左边的最高位丢弃,右边补全0(把 << 左边的数据*2的移动次幂)。
右移 >> : 把>>左边的数据/2的移动次幂。
无符号右移 >>> : 无论最高位是0还是1,左边补齐0。
当 i = 2 时
左边:31 x 2 = 62
右边:(2 << 5) - 2 = 2 x 2 ^5 -2 = 62
JVM可以用此转换方式进行高效运算