HashMap源码分析
最近在看HashMap源码分析的视频通过自己的总结和网上查找资料找到的一些东西进行分析写出来一片自己对于HashMap源码的想法
package com.example.demo;
import java.util.HashMap;
/**
* @author: yangqiang
* @create: 2020-05-14 14:43
*/
public class TestMain {
public static void main(String[] args) {
HashMap<testClass, Object> map = new HashMap<>(8);
for(int i = 0;i<100;i++){
map.put(new testClass(1),i);
}
}
}
package com.example.demo;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author: yangqiang
* @create: 2020-05-18 17:38
*/
@Data
@AllArgsConstructor
public class testClass {
private int key;
@Override
public int hashCode() {
return 1;
}
}
首先对第一个put进行源码分析
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
/**中文翻译
*将指定值与此映射中的指定键相关联。
*如果映射先前包含密钥的映射,则
*值被替换。
*与指定值关联的@param密钥
*@param value要与指定键关联的值
*@返回与<tt>键相关的上一个值,或
*<tt>如果没有<tt>键的映射,则为空。
*(A<tt>null</tt>返回也可以指示映射
*以前与键关联的<tt>null</tt>)
*/
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
/**
* Computes key.hashCode() and spreads (XORs) higher bits of hash
* to lower. Because the table uses power-of-two masking, sets of
* hashes that vary only in bits above the current mask will
* always collide. (Among known examples are sets of Float keys
* holding consecutive whole numbers in small tables.) So we
* apply a transform that spreads the impact of higher bits
* downward. There is a tradeoff between speed, utility, and
* quality of bit-spreading. Because many common sets of hashes
* are already reasonably distributed (so don't benefit from
* spreading), and because we use trees to handle large sets of
* collisions in bins, we just XOR some shifted bits in the
* cheapest possible way to reduce systematic lossage, as well as
* to incorporate impact of the highest bits that would otherwise
* never be used in index calculations because of table bounds.
*/
/**
*利用key的整个hashCode值和key的hashCode高十六位进行异或操作
* 为什么要右移16位?
*其实是为了减少碰撞,进一步降低hash冲突的几率。int类型的数值是4个字节的,右移16位异或可以同时保留高16位于低16位*的特征
*为什么要异或运算?
*首先将高16位无符号右移16位与低十六位做异或运算。如果不这样做,而是直接做&运算那么高十六位所代表的部分特征就可能
*被丢失 将高十六位无符号右移之后与低十六位做异或运算使得高十六位的特征与低十六位的特征进行了混合得到的新的数值中就*高位与低位的信息都被保留了 ,而在这里采用异或运算而不采用& ,| 运算的原因是 异或运算能更好的保留各部分的特征,如*果采用&运算计算出来的值会向1靠拢,采用|运算计算出来的值会向0靠拢
*/
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
/**
* Implements Map.put and related methods.
*
* @param hash hash for key 通过hash方法计算出来的hash值
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//判断整个数组是否为空 HashMap在未指定容量的情况下
//首次put会通过resize()方法创建一个长度为DEFAULT_INITIAL_CAPACITY=16的Node<K,V>[] table数组
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//假设为未扩容情况下 用 16-1 = 15 1111 和 hash 去进行与操作 得到的是一个 0-15之间的数字
//用这个数字下标去table查找 如果没有值就进行赋值
if ((p = tab[i = (n - 1) & hash]) == null)
//hash 为进行hash操作得到的hash值 key-value: put操作的key-value null:存储下一个节点的信息
tab[i] = newNode(hash, key, value, null);
else {
//如果通过这个数字下标查找table发现存在值则拿到table的第i个元素p
Node<K,V> e; K k;
//判断p的hash值和put操作的hash值是否相等,不一样的key也有可能存在相同的hash
//进一步判断p的key值和key是否相同 如果相同的话直接将这个Node覆盖掉原来的Node
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
//如果这个p是红黑树的话 。。。。。。
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//一直往p的next下级进行查找,binCount表示下级层数
for (int binCount = 0; ; ++binCount) {
//如果p.next == null 表示 p的下一个元素是null 则直接把插入的这个Node放到p的next下
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;
}
//如果p的某个下级元素key和进行put操作的key值相同 则直接break e里面已经存储了旧的key值对应的 Node元素
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//一直往下层寻找 e = p.next 就相当于 把p.next 赋值给 p继续向下寻找
p = e;
}
}
// 如果e!=null 说明put操作存在了相同的key 值这个时候进行value值的替换,并return会原来的value值
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//操作HashMap的次数 +1
++modCount;
//如果 这个HashMap中的元素数量超过了 容量 * 负载因子, 未指定时时16 * 0.75 时进行扩容操作
if (++size > threshold)
resize();
afterNodeInsertion(evict);
//如果到了这一步说明put操作没有相同的key值则返回null
return null;
}
/**
* Initializes or doubles table size. If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.
*
* @return the table
* 扩容或者初始化操作
*/
final Node<K,V>[] resize() {
// transient Node<K,V>[] table;
// 第一次put的时候 table是null oldCap = 0 threshold = 0
//
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
//如果oldCap>0 说明是进行扩容操作
if (oldCap > 0) {
// static final int MAXIMUM_CAPACITY = 1 << 30;
// 如果oldCap数组容量 >=MAXIMUM_CAPACITY 这个值时 HashMap将不再进行扩容操作
if (oldCap >= MAXIMUM_CAPACITY) {
//将扩容阈值提高到Integer.MAX_VALUE;2147483647
threshold = Integer.MAX_VALUE;
return oldTab;
}
//数组长度✖️2 再和MAXIMUM_CAPACITY 比较
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
// double threshold threshold ✖️ 2
newThr = oldThr << 1;
}
else if (oldThr > 0)
// initial capacity was placed in threshold
// 初始容量等于扩容阈值 阈值会自动向上取 2 的次方数
newCap = oldThr;
else {
// zero initial threshold signifies using defaults
// 第一次操作HashMap这时候map还是空的
// static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// 设置容量为默认值 16
// 阈值为 容量 * 负载因子
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
//创建一个新的Node数组
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
//把数组赋值给全局变量 table
table = newTab;
//如果oldTab不为null的话就要将原来的table赋值到新数组中
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
//如果e没有next后 把这个元素按照e.hash & (newCap - 1)来存储到新的下标位置
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
//如果是红黑树的话 。。。
((TreeNode<K,V>)e).split(this, newTab, 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;
//数组扩容为两倍后e.hash & (newCap - 1)和原来的得到的就不一样了
//(e.hash & oldCap) == 0 用来判断哪些是还存在原来的下标下的元素
//(e.hash & oldCap) == oldCap 的话判断哪些元素存在于扩容后的下标
if ((e.hash & oldCap) == 0) {
if (loTail == null)
//如果loTail 是第一次进来拿到的是一条链表的第一个元素
//直接确定链表的第一个元素
loHead = e;
else
//第二个以后的元素进来后则改变loTail 的next值来进行链表的赋值
loTail.next = e;
//每次将进来的Node数据进行赋值给loTail 用于loTail给next进行赋值操作
loTail = e;
}
else {
//(e.hash & oldCap) == oldCap 的话判断哪些元素存在于扩容后的下标
//和上面的操作一样区别是在 整个table数组中第j个链表
//区分出那些Node元素属于当前这个j下标,那些属于 j +oldCap 下标 再进行赋值
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
//把最后一个Node元素的next置为null
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
//把最后一个Node元素的next置为null
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
//返回新生成的链表数组
return newTab;
}
第一次put操作主要是进行了Node数组的创建再找到hash对应的数组下标 进行数组的赋值,第二次put操作由于我们对hash做了特殊处理所以每次的out操作对应的下标都是一样的,直接到第8次操作,这时候链表的长度为8进行链表转为红黑树操作(在转为红黑树操作之前会判断数组长度是否大于64再进行转为树操作,小于64则进行扩容操作)
/**
* Implements Map.put and related methods.
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
//如果Node数组的当前这个节点已经转为了红黑树结构。那接下来的put操作就需要往红黑树增加叶子节点了。
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);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//这时候链表的长度为9了next里头一共有8个Node元素进行链表转为红黑树操作
treeifyBin(tab, hash);
break;
}
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;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
TreeNode继承自LinkedHashMap内部类Entry<K,V>,而Entry<K,V>又继承自HashMap.Node<K,V>
/**
* Replaces all linked nodes in bin at index for given hash unless
* table is too small, in which case resizes instead.
* 转为树操作
*/
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
// static final int MIN_TREEIFY_CAPACITY = 64; 为什么是64我也不知道
// 数组长度如果小于64则进行一次扩容操作,可以将链表的长度进行缩小
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
//hash为该条链表下的最后一个Node元素
//e =tab[index = (n - 1) & hash] 获取该链表上第一个元素,即为该数组存储的这个下标的元素
//定义首尾节点hd,tl
TreeNode<K,V> hd = null, tl = null;
do {
//将该节点替换成红黑树节点
TreeNode<K,V> p = replacementTreeNode(e, null);
// 如果尾节点为空,说明还没有根节点
if (tl == null)
//把当前节点设置为首节点
hd = p;
else {
//第二次以后的转化操作时 这时已经有首尾节点了需要做的是把两个节点串联起来形成双向链表
//把这个节点的prev 即为上一个节点设置为尾结点
p.prev = tl;
//把尾节点的next 即为下一个节点设置为当前节点
tl.next = p;
}
//把当前节点设置为尾结点方便下一次操作
tl = p;
} while ((e = e.next) != null);
//到这里的hd已经是一个双向链表了
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
// For treeifyBin
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
return new TreeNode<>(p.hash, p.key, p.value, next);
}
红黑树
特性
-
每个节点要么是黑色,要么是红色。(节点非黑即红)
-
根节点是黑色。
-
每个叶子节点(NIL)是黑色。
-
如果一个节点是红色的,则它的子节点必须是黑色的。(也就是说父子节点不能同时为红色)
-
从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。(这一点是平衡的关键)
(红黑树并不是一个完美平衡二叉查找树,从图1可以看到,根结点P的左子树显然比右子树高,但左子树和右子树的黑结点的层数是相等的,也即任意一个结点到到每个叶子结点的路径都包含数量相同的黑结点(性质5)。所以我们叫红黑树这种平衡为黑色完美平衡)
/**
* Forms tree of the nodes linked from this node.
* 将链表转换成红黑树
*/
final void treeify(Node<K,V>[] tab) {
TreeNode<K,V> root = null;
for (TreeNode<K,V> x = this, next; x != null; x = next) {
//遍历链表中的每一个TreeNode,当前结点为x
next = (TreeNode<K,V>)x.next;
x.left = x.right = null;
if (root == null) {
//第一个节点进来的时候设置为root节点 设置为黑色
x.parent = null;
x.red = false;
root = x;
}
else {
//余下的节点
K k = x.key;
int h = x.hash;
Class<?> kc = null;
//从根结点开始遍历,寻找当前结点x的插入位置
for (TreeNode<K,V> p = root;;) {
int dir, ph;
K pk = p.key;
//如果当前结点的hash值小于根结点的hash值,方向dir = -1;
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
//如果当前结点的hash值大于根结点的hash值,方向dir = 1;
dir = 1;
else if ((kc == null &&
//如果x结点的key没有实现comparable接口,或者其key和根结点的key相等
//(k.compareTo(x) == 0)仲裁插入规则
(kc = comparableClassFor(k)) == null) ||
//只有k的类型K直接实现了Comparable<K>接口,才返回K的class,否则返回null,间接实现也不行。
(dir = compareComparables(kc, k, pk)) == 0)
//仲裁插入规则
dir = tieBreakOrder(k, pk);
TreeNode<K,V> xp = p;
//判断 p的左右节点是否存在不存在则进行插入。存在则继续循环
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
//dir <= 0,插入到左儿子
if (dir <= 0)
xp.left = x;
else
//dir <= 0,插入到右儿子
xp.right = x;
//插入后进行树的调整,使之符合红黑树的性质
root = balanceInsertion(root, x);
break;
}
}
}
}
moveRootToFront(tab, root);
}
/**
* Tie-breaking utility for ordering insertions when equal
* hashCodes and non-comparable. We don't require a total
* order, just a consistent insertion rule to maintain
* equivalence across rebalancings. Tie-breaking further than
* necessary simplifies testing a bit.
* System.identityHashCode() 返回的是 Object.hashcode() 而不是重写类的x.hashcode()
*/
static int tieBreakOrder(Object a, Object b) {
int d;
if (a == null || b == null ||
(d = a.getClass().getName().
compareTo(b.getClass().getName())) == 0)
d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
-1 : 1);
return d;
}
/**
* Returns the same hash code for the given object as
* would be returned by the default method hashCode(),
* whether or not the given object's class overrides
* hashCode().
* The hash code for the null reference is zero.
*
* @param x object for which the hashCode is to be calculated
* @return the hashCode
* @since JDK1.1
* 返回与给定对象相同的哈希代码
* 将由默认方法hashCode()返回,
* 给定对象的类是否重写
* 哈希代码()。
* 空引用的哈希代码为零。
* @要为其计算哈希代码的param x对象
* @返回哈希码
* @从JDK1.1开始
*/
public static native int identityHashCode(Object x);
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
TreeNode<K,V> x) {
//当前要插入树的节点
x.red = true;
//xp x.parent,xpp xp.parent,xppl xp.parent.left,xppr xp.parent.right
for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
//第一次root节点进来时的操作 根节点设置为 黑色 思维导图 1
if ((xp = x.parent) == null) {
x.red = false;
return x;
}
//第二次以后的节点进入 如果xp为黑色 或者 xp是根节点 xpp为 null 那当前节点设置为红色
else if (!xp.red || (xpp = xp.parent) == null)
return root;
//如果当前节点的parent节点为左子节点
if (xp == (xppl = xpp.left)) {
//当前节点的parent.parent右子节点不为空并且右子节点为红色
if ((xppr = xpp.right) != null && xppr.red) {
//当前节点的parent 和 当前节点的parent.parent右子节点(叔节点)设置为黑色
xppr.red = false;
xp.red = false;
//当前节点的parent.parent设置为红色
xpp.red = true;
x = xpp;
}
else {
//思维导图3的结构 第三个Node节点插进来
if (x == xp.right) {
//重新构建红黑树 这次就先不往里看了,涉及到红黑树的重排左旋
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
//重新构建红黑树 这次就先不往里看了,涉及到红黑树的重排右旋
root = rotateRight(root, xpp);
}
}
}
}
else {
if (xppl != null && xppl.red) {
xppl.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else
if (x == xp.left) {
//重新构建红黑树 这次就先不往里看了,涉及到红黑树的重排右旋
root = rotateRight(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
//重新构建红黑树 这次就先不往里看了,涉及到红黑树的重排左旋
root = rotateLeft(root, xpp);
}
}
}
}
}
}