好戏开演
重难点的HashMap源码解读 还有ConcurrentHashmap对比
HashMap核心源码解读
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
//默认的初始化容量 1*2*2*2*2=16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4
//最大容量限制 2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认负载因子 0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//链表转化为树的临界值
static final int TREEIFY_THRESHOLD = 8;
//树退化成链表的临界值
static final int UNTREEIFY_THRESHOLD = 6;
//链表转化为树的阙值,只有当链表长度超过8并且整个数组的长度超过这个,才会将链表转化为红黑树
static final int MIN_TREEIFY_CAPACITY = 64;
//存放数值的结点
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
//结点key
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;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
//计算hashCOde的方法 调用object的方法根据key以及value进行计算
public final int hashCode() {
// ^是异或运算符 将运算符两边都写成二进制,然后比较每一位的数,相同取0 不同取1 得到的结果就是运算后的结果
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
//先判断传入的对象是否是本对象, 其次判断是否是Map.Entry类型 最后再比较他们的key与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;
}
}
//计算hash值
static final int hash(Object key) {
int h;
//当传入的key不是null的时候 1 计算出key的hashCode值赋值给h 2 h右移16位 3 用h与2得到的结果进行异或运算
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//hash表,存放值的数组
transient Node<K,V>[] table;
//后续完善这个东西的作用
transient Set<Map.Entry<K,V>> entrySet;
//map的大小
transient int size;
//map的修改次数
transient int modCount;
//要调整大小的下一个大小值(容量*负载系数) 要扩容的临界值 当map容量达到这个之后,就进行扩容
int threshold;
//负载因子
final float loadFactor;
//三个构造函数
public HashMap() {
//初始化负载因子
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
public HashMap(int initialCapacity) {
//调用两个参数的构造函数
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap(int initialCapacity, float loadFactor) {
//初始化容量为0 直接抛异常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
//设置初始化容量
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
//初始化负载因子
this.loadFactor = loadFactor;
//设置扩容临界值
this.threshold = tableSizeFor(initialCapacity);
}
//好戏开演~~~~~~
//给map添加数据
public V put(K key, V value) {
//计算出key的hash值,然后调用下面方法存值
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; Node<K,V> p; int n, i;
//将map中的表赋值给tab 并进行非空判断
if ((tab = table) == null || (n = tab.length) == 0)
//当表为空,进行扩容
n = (tab = resize()).length;
//计算将要放入数组的索引 用hash表的长度-1与上一步计算出来的key的hash值做与运算
if ((p = tab[i = (n - 1) & hash]) == null)
//当前hash表的位置没有值,那么创建新的结点,并将其放在当前索引位置
tab[i] = newNode(hash, key, value, null);
else {
//当前索引存在值,也就是已经发生了hash冲突
Node<K,V> e; K k;
//判断需要存入的值的key与hash表当前索引的存放的结点的key是不是相等
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
//相等就是要将key的值替换为新的, 那么将这个结点保存在e中 后面做value的替换
e = p;
//当前结点的key与需要放入map的key不相等,首先判断这个结点是不是数结构
else if (p instanceof TreeNode)
//树节点,那么进行树结点的添加操作
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//当前结点时链表结构
for (int binCount = 0; ; ++binCount) {
//判断当前节点是否存在下一个结点,因为hash冲突之后,是以链表形式存放数据,所以每一个结点都有指向下一个结点的指针
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与需要放入map的key是不是同样的
//简单来说,就是需要判断放入map的数据的key是不是已经在这个链表中。所以需要将这个key与当前链表的所有节点的key进行比较
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
//如果key恰好在当前的链表中,跳出循环 此时,e结点的key与当前需要加入map的key是同一个key
break;
//将p指向下一个结点 继续循环判断
p = e;
}
}
//能走到这一步,说明key已经存在与链表中,并且已经将链表中的结点取出保存在了e节点中
if (e != null) { // existing mapping for key
//获取结点的旧值
V oldValue = e.value;
//判断是否允许替换,或者旧值为null
if (!onlyIfAbsent || oldValue == null)
//将结点的值替换为新的值
e.value = value;
afterNodeAccess(e);
//返回结点旧值
return oldValue;
}
}
//当新添加数据之后,将map的修改次数+1
++modCount;
//map的大小+1,并且判断map的大小是否超过扩容阙值
if (++size > threshold)
//超过阙值,进行扩容
resize();
afterNodeInsertion(evict);
return null;
}
//hash表扩容
final Node<K,V>[] resize() {
//获取旧的hash表
Node<K,V>[] oldTab = table;
//获取旧表的长度
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//获取扩容临界值
int oldThr = threshold;
//新表长度 新表的扩容临界值
int newCap, newThr = 0;
//旧表长度大于0
if (oldCap > 0) {
//旧表长度大于最大的容量
if (oldCap >= MAXIMUM_CAPACITY) {
//将旧表扩容临界值改为最大的Integer数
threshold = Integer.MAX_VALUE;
//直接返回旧表
return oldTab;
}
//将旧表长度*2 作为新表的长度 并判断是否小于最大容量 并且旧表长度是否大于等于默认的初始化容量(16)
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
//设置新表的扩容临界值为旧表扩容临界值*2
newThr = oldThr << 1; // double threshold
}
//旧表扩容临界值>0 将旧表的扩容值设置为新表的长度
else if (oldThr > 0) // initial capacity was placed in threshold
//设置新表的长度
newCap = oldThr;
else { // zero initial threshold signifies using defaults
//否则,第一次添加数据 初始化新表表为默认的容量 也就是数组长度为默认的16
newCap = DEFAULT_INITIAL_CAPACITY;
//初始化新表的扩容的临界值 默认的负载因子*默认的容量 0.75*16
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<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
//将新表赋值给原表
table = newTab;
//原表不为空
if (oldTab != null) {
//遍历原表
for (int j = 0; j < oldCap; ++j) {
//临时节点
Node<K,V> e;
if ((e = oldTab[j]) != null) {
//表的当前索引位置值不位null 将其设置为null
oldTab[j] = null;
//精华部分: 判断这个结点的下一个结点是否为null
//解释一波:因为hash表是数组加链表的形式 我们当前是遍历的数据,拿到数据指定索引的值 然而,这个node结点还有关联的next结点,他是node之间的关联关系 与数组没有任何关系哦
//就相当于你有5支铅笔,铅笔顺序排列着,但是每一支铅笔还绑定了自己的橡皮、铅笔刀等 所以这一块的next就类似于与当前铅笔绑定的橡皮、铅笔刀。
if (e.next == null)
//next为null,将当前key的hash值与新容量-1的值做与运行算,得到的结果作为索引,将当前结点放置到新表中
newTab[e.hash & (newCap - 1)] = e;
//next结点不是null ,判断当前e是不是树结构
else if (e instanceof TreeNode)
//树结构,那么使用树的方式将以前的数据放入新的表中 有点复杂,后续完善这个
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
//是链表 总结:将hash值与旧容量做与运算为0的结点挂在到新链表的j+oldCap位置 否则,挂在到原位置
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
//获取下一个节点
next = e.next;
//如果e的hash值与旧容量做与运算得到的结果为0
if ((e.hash & oldCap) == 0) {
//如果loTail没有值,
if (loTail == null)
//将e赋值给loHead 存储链表头结点
loHead = e;
else
//loTail有值
loTail.next = e;
//将e复制给loTail
loTail = e;
}
//这个else的操作同上
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
//循环,直至找到链表最后的结点
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
//将loHead挂在到新的表的j索引位置
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
//将hiHead挂在到j+旧容量为索引的位置
newTab[j + oldCap] = hiHead;
}
}
}
}
}
//返回新表
return newTab;
}
//树节点的添加操作 139行代码详解 map:本对象 tab:hash表存放值的数组 h:key的hash值 k:键 v:value
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,int h, K k, V v) {
Class<?> kc = null;
//判断子树的每个结点的key是否与传入的key相同的条件 根节点的时候判断一次旧ok 否则每一个结点的子树都需要判断
boolean searched = false;
//获取树的根节点
TreeNode<K,V> root = (parent != null) ? root() : this;
//遍历树
for (TreeNode<K,V> p = root;;) {
int dir, ph; K pk;
//当前树节点的hash值>将要存入map的k的hash值
if ((ph = p.hash) > h)
dir = -1;
//当前树节点的hash值<将要存入map的k的hash值
else if (ph < h)
dir = 1;
//两个hash值相等,判断当前结点的key与需要放入map的key是不是相同
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
//相同的话,直接返回改结点,然后做值的替换
return p;
//不是用一个key 并且k没有实现compareComparable接口,
else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) {
if (!searched) {
TreeNode<K,V> q, ch;
searched = true;
//查询改树的左子树与右子树,判断有没有结点的key与当前需要加入map的key相同 有的话,直接返回改结点
if (((ch = p.left) != null &&
(q = ch.find(h, k, kc)) != null) ||
((ch = p.right) != null &&
(q = ch.find(h, k, kc)) != null))
return q;
}
//将当前结点的key与需要加入map的key进行排序
dir = tieBreakOrder(k, pk);
}
//当前遍历的结点
TreeNode<K,V> xp = p;
//找到需要插入的树是当前树的左子树还是右子树,并且左子树||右子树结点没有值
if ((p = (dir <= 0) ? p.left : p.right) == null) {
//临时保存当前遍历节点的下一个结点
Node<K,V> xpn = xp.next;
//创建一个新的树结点
TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
if (dir <= 0)
//将新节点挂到当前遍历的树节点上
xp.left = x;
else
xp.right = x;
//将新节点挂载到当前遍历的node护额短板上岗
xp.next = x;
//设置新节点的父节点以及上一个结点 这里保存了双向链表的特征,同时又有树的特征
x.parent = x.prev = xp;
//临时保存的下一个结点不为null。
if (xpn != null)
//将临时保存的结点的上一个结点设置为新创建的结点x
((TreeNode<K,V>)xpn).prev = x;
//树中添加了新的结点,进行树的调整,以保持树的平衡,并且设置根节点为改索引位置的第一个结点
moveRootToFront(tab, balanceInsertion(root, x));
return null;
}
}
}
//151行代码详解 链表转化为红黑树
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
//如果hash表为null或者表的长度<最小树化的阙值,那么就进行扩容 而不是说链表大于等于8就树化
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
//再次确认当前索引的确有hash冲突
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);
//再次确认head结点不为null,样子并且将其放入表的当前索引位置
if ((tab[index] = hd) != null)
//树化 也就是将一个链表转化为树,这一块就跟java没关系了 无非就是判断结点值与根节点的大小,然后插入到根节点的子节点中而已
hd.treeify(tab);
}
}
//252详解 这一块会牵扯到树退化与进化问题
//在hashmap扩容之后,会将以前的数据移到新表中 这一块会牵扯hashmap的树的退化问题
// 本对象 hashmap 新的hash表 转移数据的索引 旧hash表的容量
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
//hash表需要转移到新表的索引上的第一个节点 因为需要将这个索引的数据全部移到新表中
TreeNode<K,V> b = this;
// Relink into lo and hi lists, preserving order
TreeNode<K,V> loHead = null, loTail = null;
TreeNode<K,V> hiHead = null, hiTail = null;
int lc = 0, hc = 0;
//循环遍历这个索引上的全部结点
for (TreeNode<K,V> e = b, next; e != null; e = next) {
//临时保存下一个结点
next = (TreeNode<K,V>)e.next;
//将遍历中的这个节点的下一个结点置为空
e.next = null;
//计算是高结点还是低结点 简单来说,就是将这个树分为两部分而已
if ((e.hash & bit) == 0) {
//设置loHead ,并且挂在上lotail
if ((e.prev = loTail) == null)
loHead = e;
else
loTail.next = e;
loTail = e;
//记录数据数量,用来判断是否需要进行链表化
++lc;
}
else {
//同上面 设置hiHead 挂在上hiTail
if ((e.prev = hiTail) == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
++hc;
}
}
//loHead不为空
if (loHead != null) {
//如果loHead中的节点数小于等于树退化的临界值
if (lc <= UNTREEIFY_THRESHOLD)
//进行树的退化
tab[index] = loHead.untreeify(map);
else {
//进入else 说明loHead中挂在的结点数量>8 但是这个不一定是树结构
tab[index] = loHead;
//走到这一步,说明树结构已经被破坏
//因为在上面进行高低结点拆分的时候,已经破坏了树的结构
//极限情况下, 这个结点全部都是高结点 或者全部都是低结点 那么这个树结构不会变
//但是现在既有loHead,也有hiHead,说明树结构被破坏了
if (hiHead != null) // (else is already treeified)
//将loHead进行树化
loHead.treeify(tab);
}
}
//这里的判断跟上面loHead相同
if (hiHead != null) {
if (hc <= UNTREEIFY_THRESHOLD)
tab[index + bit] = hiHead.untreeify(map);
else {
tab[index + bit] = hiHead;
if (loHead != null)
hiHead.treeify(tab);
}
}
}
//移除指定的key
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value;
}
//移除key为指定key的结点
final Node<K,V> removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
//hash表不为空 并且根据传入的key计算出来的表的索引位置存在结点
if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
//如果当前索引的第一个结点的key就是我们需要移除的key
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
//将这个结点保存在临时节点中
node = p;
//当前索引存放的结点存在下一个结点
else if ((e = p.next) != null) {
//p结点是树结构
if (p instanceof TreeNode)
//从树中查找key为我们需要移除的结点
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
//p结点是链表
do {
//p的下一个结点e是我们需要移除的结点
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
//将结点保存在临时节点中
node = e;
break;
}
//e不是我们需要移除的结点,指针后移,遍历后面的结点
p = e;
} while ((e = e.next) != null);
}
}
//如果查到了我们需要移除的结点
if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) {
//结点是树结点 从树中移除
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
//如果需要移除的结点是第一个结点
else if (node == p)
//将这个结点的下一个结点直接放入表中 也就相当于移除了此节点
tab[index] = node.next;
else
//移除的结点不是第一个结点,那么在循环找移除的节点的时候,p也在往后移,此时p是我们需要移除的结点的前结点
//直接将p的下一结点指向我们需要移除的节点的下一个结点 也就相当于移除了此节点
p.next = node.next;
//hash表的修改次数+1
++modCount;
//hash表的长度-1
--size;
afterNodeRemoval(node);
//返回移除的结点
return node;
}
}
return null;
}
}