目录
1.类声明
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
- 实现 java.util.Map 接口,并继承 java.util.AbstractMap 抽像类。
- 实现 java.io.Serializable 接口。
- 实现 java.lang.Cloneable 接口。
2.属性
/**
* 底层存储的数组
*/
transient Node<K,V>[] table;
/**
* 调用 `#entrySet()` 方法后的缓存
*/
transient Set<Map.Entry<K,V>> entrySet;
/**
* key-value 的键值对数量
*/
transient int size;
/**
* HashMap 的修改次数
*/
transient int modCount;
/**
* 阀值,当 {@link #size} 超过 {@link #threshold} 时,会进行扩容
*/
int threshold;
/**
* 扩容因子
*/
final float loadFactor;
3.构造方法
1.HashMap()
构造方法,创建一个初始化容量为 16 的 HashMap 对象
/**
* 默认的初始化容量
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* 默认加载因子为 0.75
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 创建一个初始化容量为 16 的 HashMap 对象
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
- 初始化
loadFactor
为DEFAULT_LOAD_FACTOR = 0.75
。 - 在该构造方法上,我们并没有看到
table
数组的初始化。它是延迟初始化,在我们开始往 HashMap 中添加 key-value 键值对时,在#resize()
方法中才真正初始化。
2.HashMap(int initialCapacity)
方法,初始化容量为 initialCapacity
的 HashMap 对象
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
内部调用 #HashMap(int initialCapacity, float loadFactor)
构造方法。
3.HashMap(int initialCapacity, float loadFactor)
构造方法,初始化容量为 initialCapacity
、加载因子为 loadFactor
的 HashMap 对象
/**
* 最大的容量为 2^30 。
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
public HashMap(int initialCapacity, float loadFactor) {
// 校验 initialCapacity 参数
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
// 避免 initialCapacity 超过 MAXIMUM_CAPACITY
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
// 校验 loadFactor 参数
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// 设置 loadFactor 属性
this.loadFactor = loadFactor;
// 计算 threshold 阀值
this.threshold = tableSizeFor(initialCapacity);
}
static final int tableSizeFor(int cap) {
// 将 cap 从最高位(最左边)第一个为 1 开始的位开始,全部设置为 1 。
int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
// 因为 n 已经是 0..01..1 的情况,那么 n + 1 就能满足 cap 的最小 2 的 N 次方
// 在 cap 为 0 和 1 的时候,n 会为 -1 ,则此时最小 2 的 N 次方为 2^0 = 1 。
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
4.HashMap(Map<? extends K, ? extends V> m)
构造方法,创建 HashMap 对象,并将 c
集合添加到其中
public HashMap(Map<? extends K, ? extends V> m) {
// 设置加载因子
this.loadFactor = DEFAULT_LOAD_FACTOR;
// <X> 批量添加到 table 中
putMapEntries(m, false);
}
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
// 如果 table 为空,说明还没初始化,适合在构造方法的情况
if (table == null) { // pre-size
// 根据 s 的大小 + loadFactor 负载因子,计算需要最小的 tables 大小
float ft = ((float)s / loadFactor) + 1.0F; // + 1.0F 的目的,是因为下面 (int) 直接取整,避免不够。
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
// 如果计算出来的 t 大于阀值,则计算新的阀值
if (t > threshold)
threshold = tableSizeFor(t);
// 如果 table 非空,说明已经初始化,需要不断扩容到阀值超过 s 的数量,避免扩容
} else {
// Because of linked-list bucket constraints, we cannot
// expand all at once, but can reduce total resize
// effort by repeated doubling now vs later
while (s > threshold && table.length < MAXIMUM_CAPACITY)
resize(); // 扩容
}
// 遍历 m 集合,逐个添加到 HashMap 中。
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}
4.Hash函数
// 计算 key 的哈希值
static final int hash(Object key) {
int h;
// ^ (h >>> 16) 高 16 位与自身进行异或计算,保证计算出来的 hash 更加离散
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
原理参考:JDK 源码中 HashMap 的 hash 方法原理是什么? - 知乎
5.添加单个元素
public V put(K key, V value) {
// hash(key) 计算哈希值
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; // tables 数组
Node<K,V> p; // 对应位置的 Node 节点
int n; // 数组大小
int i; // 对应的 table 的位置
// 如果 table 未初始化,或者容量为 0 ,则进行扩容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize() /*扩容*/ ).length;
// 如果对应位置的 Node 节点为空,则直接创建 Node 节点即可。
if ((p = tab[i = (n - 1) & hash] /*获得对应位置的 Node 节点*/) == null)
tab[i] = newNode(hash, key, value, null);
// 如果对应位置的 Node 节点非空,则可能存在哈希冲突
else {
Node<K,V> e; // key 在 HashMap 对应的老节点
K k;
// 如果找到的 p 节点,就是要找的,则直接使用即可
if (p.hash == hash && // 判断 hash 值相等
((k = p.key) == key || (key != null && key.equals(k)))) // 判断 key 真正相等
e = p;
// 如果找到的 p 节点,是红黑树 Node 节点,则直接添加到树中
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// 如果找到的 p 是 Node 节点,则说明是链表,需要遍历查找
else {
// 顺序遍历链表
for (int binCount = 0; ; ++binCount) {
// `(e = p.next)`:e 指向下一个节点,因为上面我们已经判断了最开始的 p 节点。
// 如果已经遍历到链表的尾巴,则说明 key 在 HashMap 中不存在,则需要创建
if ((e = p.next) == null) {
// 创建新的 Node 节点
p.next = newNode(hash, key, value, null);
// 链表的长度如果数量达到 TREEIFY_THRESHOLD(8)时,则进行树化。
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break; // 结束
}
// 如果遍历的 e 节点,就是要找的,则则直接使用即可
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break; // 结束
// p 指向下一个节点
p = e;
}
}
// 如果找到了对应的节点
if (e != null) { // existing mapping for key
V oldValue = e.value;
// 修改节点的 value ,如果允许修改
if (!onlyIfAbsent || oldValue == null)
e.value = value;
// 节点被访问的回调
afterNodeAccess(e);
// 返回老的值
return oldValue;
}
}
// 增加修改次数
++modCount;
// 如果超过阀值,则进行扩容
if (++size > threshold)
resize();
// 添加节点后的回调
afterNodeInsertion(evict);
// 返回 null
return null;
}
6.扩容
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
// oldCap 大于 0 ,说明 table 非空
if (oldCap > 0) {
// 超过最大容量,则直接设置 threshold 阀值为 Integer.MAX_VALUE ,不再允许扩容
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// newCap = oldCap << 1 ,目的是两倍扩容
// 如果 oldCap >= DEFAULT_INITIAL_CAPACITY 满足,说明当前容量大于默认值(16),则 2 倍阀值。
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
//【非默认构造方法】oldThr 大于 0 ,则使用 oldThr 作为新的容量
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
//【默认构造方法】oldThr 等于 0 ,则使用 DEFAULT_INITIAL_CAPACITY 作为新的容量,使用 DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY 作为新的容量
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 如果上述的逻辑,未计算新的阀值,则使用 newCap * loadFactor 作为新的阀值
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
// 将 newThr 赋值给 threshold 属性
threshold = newThr;
// 创建新的 Node 数组,赋值给 table 属性
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
// 如果老的 table 数组非空,则需要进行一波搬运
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
// 获得老的 table 数组第 j 位置的 Node 节点 e
Node<K,V> e;
if ((e = oldTab[j]) != null) {
// 置空老的 table 数组第 j 位置
oldTab[j] = null;
// 如果 e 节点只有一个元素,直接赋值给新的 table 即可
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
// 如果 e 节点是红黑树节点,则通过红黑树分裂处理
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
// 如果 e 节点是链表
else { // preserve order
// HashMap 是成倍扩容,这样原来位置的链表的节点们,会被分散到新的 table 的两个位置中去
// 通过 e.hash & oldCap 计算,根据结果分到高位、和低位的位置中。
// 1. 如果结果为 0 时,则放置到低位
// 2. 如果结果非 1 时,则放置到高位
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
// 这里 do while 的原因是,e 已经非空,所以减少一次判断。细节~
do {
// next 指向下一个节点
next = e.next;
// 满足低位
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
// 满足高位
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
// 设置低位到新的 newTab 的 j 位置上
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
// 设置高位到新的 newTab 的 j + oldCap 位置上
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
7.树化
/**
* 每个位置链表树化成红黑树,需要的链表最小长度
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* HashMap 允许树化最小 key-value 键值对数
*/
static final int MIN_TREEIFY_CAPACITY = 64;
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
// 如果 table 容量小于 MIN_TREEIFY_CAPACITY(64) ,则选择扩容
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
// 将 hash 对应位置进行树化
else if ((e = tab[index = (n - 1) & hash]) != null) {
// 顺序遍历链表,逐个转换成 TreeNode 节点
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
// 树化
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
总结:
1.数据结构:HashMap是数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,链表长度大于8时转为红黑树
2.扩容:扩容后的大小一定是2的N次方,这样设计是因为为了减少Hash冲突
参考美团技术团队: