HashMap 重要源码分析(含代码、含图、多注释去帮助理解!!!)
0. 简介
HashMap,
-
存放键值对,是非线程安全的。
-
可以存储
null的key和value,但null作为键只能有一个,null作为值可以有多个 -
由 数组+链表 组成的,但当链表长度超过阈值(默认为 8)会将链表转化为红黑树。(提高搜索效率)
-
默认初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。
-
总是使用 2 的幂作为哈希表的大小。
1. 类声明(public)
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
2. 6 个常量属性(static final)
- 默认初始容量 1<<4 16
- 最大容量 1<<30 1073741824 ( 约 Integer.MAX_VALUE 的一半 )
- 默认装载因子 0.75f
- 转红黑树的阈值 8
- 转链表的阈值 6
- 转红黑树时数组所需的最小容量 64
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final int MAXIMUM_CAPACITY = 1 << 30;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
static final int TREEIFY_THRESHOLD = 8;
static final int UNTREEIFY_THRESHOLD = 6;
static final int MIN_TREEIFY_CAPACITY = 64;
3. Node<K, V> 静态内部类(static)
1. 类定义
static class Node<K,V> implements Map.Entry<K,V> {}
2. 4 个属性
hash, key, value, next
3. 重要方法
int hashCode()
重写 Object 的 hashCode() 方法!使用 Objects 类的 hashCode() 方法分别求 key 和 value 的 hashCode,再相与(^)。
boolean equals(Object o)
重写 equals() 方法!
- 判断对象是否 = 相等
- 判断对象是否 instanceof Map.Entry
- 使用 Objects.equals() 判读是否 key 和 value 相同
源码
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
// get() set() and toString() 省略
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
4. 静态方法(static)
int hash(Object key)
key.hashCode() ^ ( key.hashCode() >>> 16 )
// 根据key 进行 hash注意:使用的是 Object 的 hashCode() 方法
boolean comparableClassFor(Object x)
判断该对象能否比较
boolean compareComparables(...Object k, Object x)
通过 Comparable 的 compareTo判断两个对象是否相同
int tableSizeFor(int cap)
通过 >>> 和 ^ 运算,计算 >= cap 的 2 的幂返回
源码
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
static Class<?> comparableClassFor(Object x) {
if (x instanceof Comparable) {
Class<?> c;
Type[] ts, as; Type t; ParameterizedType p;
if ((c = x.getClass()) == String.class)
// bypass checks
return c;
if ((ts = c.getGenericInterfaces()) != null) {
for (int i = 0; i < ts.length; ++i) {
if (((t = ts[i]) instanceof ParameterizedType) && ((p = (ParameterizedType)t).getRawType() == Comparable.class) && (as = p.getActualTypeArguments()) != null && as.length == 1 && as[0] == c)
// type arg is c
return c;
}
}
}
return null;
}
@SuppressWarnings({"rawtypes","unchecked"})
// for cast to Comparablestatic
int compareComparables(Class<?> kc, Object k, Object x) {
return (x == null || x.getClass() != kc ? 0 : ((Comparable)k).compareTo(x));
}
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;
}
5. 6 个属性
- 表数组:transient Node<K, V>[] table
- 非空的桶的集合:transient Set<Map.Entry> set
- 表数组中非空桶的个数:transient int size
- 扩容阈值:int threshold(capacity * load factor)(未分配表数组时,此字段保存初始容量或为 0 )
- 修改次数:transient int modCount
- 装载因子:final float loadFactor
transient Node<K,V>[] table;
transient Set<Map.Entry<K,V>> entrySet;
transient int size;
transient int modCount;
int threshold;
final float loadFactor;
6. 4 个构造方法
- 有参,初始容量、装载因子
- 有参,初始容量(使用默认装载因子)
- 无参(使用默认装载因子)
- 有参,一个 Map(使用默认装载因子,调用 putMapEntries(m, false) 方法)
//省略,自行看源码
7. 方法
void putMapEntries(m, evict)
// 将传入的 map 中的元素存入当前 map 的 table 中
- 判断 table 是否未分配,若未分配则求数值 t ,t 为在当前 loadFactor下足够存放 m 的元素而不至于扩容的数值(要求 <= MAXIMUM_CAPACITY )
- 若 t > threshold 则将 threshold 设置为 >= t 的 2 的幂(注意:当前 table 未分配,threshold 保存的是初始容量或 0 )
- 否则,若 m 的大小 > threshold 则 说明无论如何 HashMap 至少需要进行一次扩容的,因此调用 resize() 扩容一次
- 遍历 m,将 m 的元素的 hash(key), key, value... 作为入参调用 putVal()
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
if (table == null) {
// pre-size
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY);
if (t > threshold)
threshold = tableSizeFor(t);
}
else if (s > threshold)
resize();
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);
}
}
}
V putVal(hash, key, value, onlyIfAbsent, evict)
p 为 (n-1)&hash 计算到的表数组的位置的引用;
i 为定位到的表数组的下标;
n 为表数组长度;
e 为 key:value 应写入的位置的引用(可能在表数组上、链表上或红黑树上);
onlyIfAbsent 不存在才 put key:value
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//若table未分配则调用resize()分配 table
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//(n-1)&hash 算出表数组的下标i,p设为该位置的引用
// 若 p==null 则在 i 位置 newNode存入该key:value
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
//若 table已分配且 p 已存有值
else {
Node<K,V> e; K k;
//若 p 中存的key 和 需存入的key的hash以及key相等或equals,则 e=p
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//若p为红黑树节点则调用putTreeVal()确定e
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//若p不为需写入的位置,则遍历链表找到e的位置
else {
for (int binCount = 0; ; ++binCount) {
//遍历到链表末尾未发现可替换的
if ((e = p.next) == null) {
//在链表末尾newNode,此时e=null
p.next = newNode(hash, key, value, null);
//若链表长度超过15,则调用treeifyBin()转红黑树
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 = e;
}
}
if (e != null) { // existing mapping for key,若e!=null说明找到了可替换的节点
V oldValue = e.value;
//onlyIfAbsent=false 或者 oldValue=null 替换节点e的value
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//程序走到这说明 原table中 没有可替换的 key:value
//此时 修改次数+1 ,若hashmap的大小超过阈值则resize()
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
V put(key, value)
调用 putVal(key, value, false, false) 存元素
Node<K, V> getNode(hash, key)
first 为 key 在 table 上的映射的引用
e 为找到映射位置时遍历链表的引用,即用 e 遍历链表,类似与 while( e != null ){ e = e.next ; }
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//首先,判断table已分配 且 table长度大于0 且 (n-1)&hash的位置有值
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//first便是要找的 key:value则返回 value
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//first.next!=null
if ((e = first.next) != null) {
if (first instanceof TreeNode)
//first若为树节点则调用getTreeNode()在红黑树上查找 key对应的value并返回
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
//遍历first链表直到找到 key 对应的value返回
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
//找不到对应的key
return null;
}
V get(key)
调用 getNode(hash(key), key)
Node<K, V> resize()
-
若旧容量 > 0 (正常扩容)
-
若旧容量达到最大容量,将阈值设为 ,返回旧 table
-
新容量为旧容量 2 倍,若新容量 < 最大容量 且 旧容量 >= 默认初始容量,新阈值为旧阈值 2 倍
-
若旧容量 <= 0 但 旧阈值 > 0(说明 table 未分配,旧阈值即为 HashMap 的初始化容量,此时需要通过 resize() 分配 table)
-
新容量 = 旧阈值
-
若旧容量 <= 0 但 旧阈值 <= 0(说明 table 未分配,且初始化容量不合法)
-
使用新容量 = 默认的初始容量,新阈值 = 默认阈值
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,oldThr,newCap,newThr if (oldCap > 0) { //oldCap>0,表示table已分配 //oldCap达到最大容量则将threshold设定为int最大值,table将不再扩容 if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } //此时更新容量 newCap=2*oldCap //若新容量小于最大容量且oldCap达到了默认初始容量,newThr=2*oldThr else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } //oldCap<=0说明table未分配,oldThr此时为最初的初始化容量,若oldThr>0,则使用该初始化容量newCap=oldThr else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; //oldThr=0初始化容量为0,则使用HashMap默认的初始化容量和阈值 else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } //新阈值newThr未更新,说明新容量更新了但新容量大于最大容量 或 原容量未达到16;或者说明table未初始化但newCap=oldThr>0 //ft=newCap * loadFactor if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; //new 新table,遍历旧table @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { //遍历旧table for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; //若当前位置元素没有next节点则直接rehash放入新table if (e.next == null) newTab[e.hash & (newCap - 1)] = e; //若当前位置元素为树节点 else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); //若当前位置形成链表,将该链表分为low和high两条链表 分别 放入原下标j位置和j+oldCap位置 else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { 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); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
void treeifyBin()
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
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);
}
}
8. 内部类 TreeNode<K, V>
1. 类声明
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
2. 5 个重要属性
TreeNode<K,V> parent; // 红黑树的链接 red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // 需要在下一次删除时断开链接needed to unlink next upon deletion
boolean red;
3. 方法
void treeify()
final void treeify(Node<K,V>[] tab) {
TreeNode<K,V> root = null;
//遍历树结点,x为当前遍历结点 next为下一结点
for (TreeNode<K,V> x = this, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
x.left = x.right = null;
//若root结点为空,则当前结点x不为红且父结点为空,将当前结点设为root结点
//此时当前结点x为root结点
if (root == null) {
x.parent = null;
x.red = false;
root = x;
}
//当前结点不为root结点时
else {
K k = x.key;
int h = x.hash;
Class<?> kc = null;
//p=root
//在for循环中 p为计划放入的结点
for (TreeNode<K,V> p = root;;) {
int dir, ph;
K pk = p.key;
//hash>上一个结点.hash: dir=-1
if ((ph = p.hash) > h)
dir = -1;
//hash<上一个结点.hash: dir=1
else if (ph < h)
dir = 1;
//key为没有实现Comparable接口 或 key可比较但比较发现 root.key和当前key相等
//此时调用tieBreakOrder()方法进行比较,若仍比较为相等则判断为 上一个结点key > 当前key
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk);
//此时已经判断好当前结点x 和 上一个结点结点 的key的大小
//xp=root
TreeNode<K,V> xp = p;
//若当前结点x.key小则将x放入 上一个结点的左结点,否则放右结点,前提是准备放入的位置为空,否则(p指向准备放入的结点,继续遍历结点寻找位置放入
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
//找到当前结点x可放入的位置并放入后,调用balenceInsertion()方法平衡红黑树
root = balanceInsertion(root, x);
break;
}
}
}
}
moveRootToFront(tab, root);
}
\