标签:csdn hashset 解决 return 参与 开放 定义 int 也有
HashMap 是集合框架中最重要的类之一:
哈希表解决冲突的方式有两种:
transient Node<K,V>[] table;
这个 Node 就是链表,在长度超过8的时候会转换为 TreeNode。
注意下,Node 会在第一次计算元素 hash 值后,把 hash 记录下来,扩容时可以复用。
在看 java.util.HashMap#put 源码之前,我以为会用 mod 计算元素在 table 中的索引:
i = hash % n
// hash:通过 hash() 计算出来的哈希值
// n:table.length
然而实际情况是 i = (n-1) & hash
,网上很多说法这是用二进制实现的高性能取余。究竟是不是,试一下就知道:
int[] ns = new int[]{16, 15};
for (int n : ns) {
System.out.println("----" + n + "----");
System.out.println(100 % n);
System.out.println(100 & n - 1);
}
结论很明显,这不是一般的取余,而是当 n 为 power of 2 恰好表现为取余。
联想一下子网掩码的工作原理,答案就出来了:这是把 n - 1
作为一个 mask 来计算索引。
为什么恰好是 n - 1 呢,可以看一个案例:
(a) 是 resize 之前的情况,key1 的索引是 0101,key2 的坐标是 0101,很好,这俩属于同一个 bucket;
(b) 是 resize 之后的情况,由于 "length is always a power of two",n - 1
扩容后只需要简单左移1位,这会儿 mask 一下你会发现,扩容后 key1 的新坐标是 0101,key2 的新坐标是 1101,它们只是新增了一个最高位。
上文我们提到,Node 会把 hash 记录下来,现在扩容,只需看看hash值中对应 mask 最高位:0则索引不变,1则 新索引 = 原索引 + oldCap
。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
Java 的 hashCode 是一个int类型,占用4字节,即32位,分为高16位,低16位。
计算索引的公式是 (n - 1) & hash
,当 table 还没扩容时,n - 1 很小,& 只能对低位有效果。
为了保证 hashcode 的高低位都能参与到索引计算中,hash() 将 hashCode 高16位 和 低16位 做异或运算,同时控制了开销。
(n - 1) & hash
不是为了高性能取余,而是对 hash 做 mask 计算索引[1]Java8系列之重新认识HashMap
[2]散列表-哈希冲突
标签:csdn hashset 解决 return 参与 开放 定义 int 也有
原文地址:https://www.cnblogs.com/mougg/p/12769967.html