HashMap底层数据结构
HashMap的put原理
JDK1.8中,HashMap采用位桶+链表+红黑树实现,当map集合中总数量达到64个并且链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
HashMap代码
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
扰动函数:
static final int hash(Object key) {
int h;
//通过hashCode()的高16位异或低16位实现
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
- 使用key.hashCode()计算hash值并赋值给变量h;
- 将h向右移动16位;
- 将变量h和向右移16位的h做异或运算(二进制位相同为0,不同为1)。
此时得到经过扰动函数后的hansh值。
将key.hashCode()右移16位:
右移16位正好为32bit的一半,自己的高半区和低半区做异或,是为了混合原始哈希码的高位和低位,来加大低位的随机性。而且混合后的低位掺杂了高位的部分特征,使高位的信息也被保留下来
putVal方法:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//底层存储数据的数组tab为空,进行第一次扩容,同时初始化tab
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//计算要存储数据的下标index,如果当前位置为null,直接插入
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//tab[i]的首个元素就是key,直接覆盖
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//tab[i]为TreeNode,进行红黑树的插入
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//链表遍历
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//链表长度大于8,转换为红黑树进行处理
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//找到已经存在的key,直接覆盖
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//数据插入完成,size超过了扩容的阀值(容量*负载因子),进行扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
HashMap解决hash冲突:
- 使用链地址法(使用散列表)来链接拥有相同hash值的数据;
- 使用2次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均;
- 引入红黑树进一步降低遍历的时间复杂度,使得遍历更快;