使用此公式如何详细计算给定键的哈希码
在String
这种情况下,计算方式String#hashCode();
如下:
public int hashCode() {
int h = hash;
int len = count;
if (h == 0 && len > 0) {
int off = offset;
char val[] = value;
for (int i = 0; i < len; i++) {
h = 31*h + val[off++];
}
hash = h;
}
return h;
}
基本上遵循java doc中的等式
hashcode = s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
关于这个实现的一个有趣的事情是它String
实际上缓存了它的哈希码。它可以做到这一点,因为String
它是不可变的。
如果我计算String
“Amit”的哈希码,它将产生这个整数:
System.out.println("Amit".hashCode());
> 2044535
让我们通过一个简单的 put to a map,但首先我们必须确定地图是如何构建的。关于 Java 最有趣的事实HashMap
是它总是有 2^n 个桶。所以如果你调用它,默认的桶数是16,显然是2^4。
在这个map上做put操作,首先会得到key的hashcode。在这个哈希码上发生了一些花哨的比特旋转,以确保糟糕的哈希函数(尤其是那些在低位上没有差异的函数)不会“过载”单个存储桶。
实际负责将密钥分发到存储桶的真正功能如下:
h & (length-1); // length is the current number of buckets, h the hashcode of the key
这仅适用于两个存储桶大小的幂,因为它使用 & 将密钥映射到存储桶而不是模数。
“Amit”将被分配到第 10 个桶,因为比特旋转。如果没有一点玩弄,它会进入第 7 个桶,因为2044535 & 15 = 7
.
现在我们有了它的索引,我们可以找到存储桶。如果桶包含元素,我们必须遍历它们并在找到时替换相等的条目。如果在链表中没有找到任何项目,我们将把它添加到链表的开头。
下一个重要的事情HashMap
是调整大小,所以如果地图的实际大小超过阈值(由当前的桶数和负载因子确定,在我们的例子中为 16*0.75=12),它将调整后备数组的大小。调整大小始终为 2 * 当前存储桶数,保证为 2 的幂,以不破坏查找存储桶的功能。
由于桶的数量发生了变化,我们必须重新散列表中的所有当前条目。这是相当昂贵的,所以如果你知道有多少项目,你应该HashMap
用那个计数来初始化它,这样它就不必一直调整大小。