前言
Java集合当中HashMap应该是尤为突出的,使用频繁,实现机制巧妙。想要通过看源码了解所有实现原理需要有很深度的基础功底,鄙人不才只看懂1、2,这里主要以jdk1.8为例,将看懂的做个总结。
jdk怎么说?
精妙之处
关键变量
DEFAULT_INITIAL_CAPACITY 默认table数组大小16(根据实际情况可调整)
DEFAULT_LOAD_FACTOR 默认装载因子0.75
TREEIFY_THRESHOLD 默认链表转树链表长度阈值之一8
UNTREEIFY_THRESHOLD 默认树转链表阈值6
MIN_TREEIFY_CAPACITY 默认链表转树数组长度阈值之二64
数据结构
hashMap底层数据结构是数组+链表(到达阈值升级为红黑树),结构体为:
Node<K,V> implements Map.Entry<K,V> {
//hash值
final int hash;
final K key;
V value;
//链表结构,下一个节点的指针
Node<K,V> next;
}
table为此数据结构的引用:transient Node<K,V>[] table;
这里还有一点就是table的长度为2的整数次幂,在创建HashMap时如果初始化长度不是2的整数次幂,也会向上取整转为2的整数次幂,比如new HashMap(10),其实初始化结果为16。这个时怎么做到的呢?如下代码:
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
key值hash算法
当插入一条记录时,首先做的就是计算key的hash值计算,代码如下:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
(h = key.hashCode()) ^ (h >>> 16)代码的实质为:将hashcode返回的是32位的整型中的高16位和低16进行异或操作,并且高16不变,这样操作有几点优势:
- 位运算效率肯定要比取模高
- 可以是key分布均匀减少hash冲突
扩容机制
HashMap在什么情况下会进行扩容?在什么情况下会进行链表转红黑树呢?
扩容有两种情况:
- 当table数组的容量 > table数组的长度 * 扩容因子
- 当链表长度 >= TREEIFY_THRESHOLD(这里以默认值为例) && table数组长度 < MIN_TREEIFY_CAPACITY(这里以默认值为例)
链表转红黑树的条件也就出来了:
当链表长度 >= TREEIFY_THRESHOLD(这里以默认值为例) && table数组长度 >= MIN_TREEIFY_CAPACITY(这里以默认值为例)
当容量扩完之后会进行resize()操作,即将原来table中的数据迁移到新的table中,其中会对key进行重新hash:
newTab[e.hash & (newCap - 1)] = e;
如果是链表的话此行代码限制了新的hash值只可能为原来值或者为原来+原table长度。
未完待续…