这是如何使用以下公式详细计算给定键的哈希码:
对于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文档中的公式。
hashcode = s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
需要注意的是,这个实现中有一个有趣的事情,那就是String实际上缓存了它的哈希码。它可以这样做,因为String是不可变的。
如果我计算字符串"Amit"的哈希码,它将会得到这个整数:
System.out.println("Amit".hashCode());
> 2044535
让我们先了解一下简单的将数据放入 map 中的过程,但首先我们必须确定 map 是如何构建的。关于 Java HashMap 最有趣的事实是它总是具有 2^n 个桶。因此,如果你调用它,默认桶的数量为 16,这显然是 2^4。
对这个 map 进行 put 操作时,它首先会获取 key 的哈希码。对这个哈希码进行了一些奇怪的位运算操作,以确保贫弱的哈希函数(特别是那些在低位上没有区别的哈希函数)不会“超载”一个单独的桶。
真正负责将您的键分配到桶中的函数如下:
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倍,这保证了是二的幂次方,以不破坏查找桶的功能。
由于桶数改变,我们必须重新散列表中所有当前条目。这非常耗费时间,因此,如果您知道有多少项,请使用该计数初始化HashMap,这样它就不必一直调整大小。
java.util.HashMap的确切行为,为什么不阅读其源代码呢?或者在调试器中逐步执行它的方法呢? - meriton