继承关系
重要的成员及内部类
Node类
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
TreeNode类
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> extends Node{
TreeNode<K,V> parent; //父节点
TreeNode<K,V> left; //左孩子
TreeNode<K,V> right; //右孩子
TreeNode<K,V> prev; //链表的前置节点
boolean red; //红黑树标志
TreeNode是红黑树,也是一个链表
成员
//hash表
transient Node<K,V>[] table;
//扩容阈值
int threshold;
//负加载因子
final float loadFactor;
//最小的 table[i] 树化 table的长度要求(table长度小于64之前 table[i] 元素个数一般不会超过8)
static final int MIN_TREEIFY_CAPACITY = 64;
//树蜕化成链表长度阈值
static final int UNTREEIFY_THRESHOLD = 6;
//table[i] 链表树化阈值
static final int TREEIFY_THRESHOLD = 8;
//默认负加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//new HashMap();默认table的长度
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//table长度达到MAXIMUM_CAPACITY之后直接Integer.MAX_VALUE
static final int MAXIMUM_CAPACITY = 1 << 30;
内部数据结构呈现
源码
put(K key, V value)
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
hash(key)扰动函数
//如果key是null一定放在table[0],因为任何数与0都为0
//否则返回key的hashcode 异或 hashcode无符号右移16位
//这样做的目的让hash值区分度更高,让高16为参与低16运算
//原理:11111111 10101010 10101010 11111111
//异或:00000000 00000000 10101010 10101010
// 11111111 10101010 10101010 01010101
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict)
//hash:通过扰动后的值
//onlyIfAbsent:是否只有存在的时候替换
//evict:后置通知标志
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
//tab:将要添加数据的数组;p:对应数组下标位置的头结点
Node<K,V>[] tab; Node<K,V> p;
//n:数组的长度;i:对应数组的下标
int n, i;
//如果数组没有初始化或者数组长度为0,进行扩容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
//如果数组的i下标位置没有元素,直接添加node节点
tab[i] = newNode(hash, key, value, null);
else {
//数组i位置有数据,有数据要么是node链表,要么是红黑树
Node<K,V> e; K k;
//判断table[i]个元素是否和插入的元素一致
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//如果一致,把table[i]的元素赋值给e
e = p;
else if (p instanceof TreeNode)
//如果table[i]节点属于红黑树,直接增加元素
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//其他是链表;咱们知道p就是table[i]的头节点
for (int binCount = 0; ; ++binCount) {
//如果找到了table[i]对应链表的尾节点
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//链表下标大于等于7之后,也就链表长度大于等于8,进行树化
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//如果增加的元素已存在相同元素,直接跳过
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//上面都不满足把下一个节点复制为p
p = e;
}
}
//存在相同的key和hash(更新)
if (e != null) {
V oldValue = e.value;
//如果需要替换或者原来的对象为null,用新的value替换旧的value值
if (!onlyIfAbsent || oldValue == null)
e.value = value;
//更新后置通知
afterNodeAccess(e);
return oldValue;
}
}
//容器元素变化+1
++modCount;
//超过扩容阈值
if (++size > threshold)
//扩容
resize();
//插入节点后置通知
afterNodeInsertion(evict);
return null;
}
treeifyBin(Node<K,V>[] tab, int hash)
//进行树化
final void treeifyBin(Node<K,V>[] tab, int hash) {
//定义table的长度n,根据hash求出需要树化的table[j]位置index
//定义遍历table[index] 链表的对象
int n, index; Node<K,V> e;
//如果table的长度小于64需要扩容分散
//即使table[n]元素个数大于等于8,扩容后会进行切开分布,链表长度会小于等于8了
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
//不清楚这里为什么要做这个判断,后续会继续看下为什么这么做
//如果只是为e赋值的话完全可以不要else if,应该是哪里存在table[index]为null的情况
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
//如果尾节点为null,把p赋值给头节点
if (tl == null)
hd = p;
else {
//还是熟悉的尾插法,双向链表
//p的前一个节点为尾节点,
p.prev = tl;
//尾节点的下一个节点尾p
tl.next = p;
}
//记录尾节点
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
//真正树化的方法
hd.treeify(tab);
}
}
resize() 扩容
//扩容
final Node<K,V>[] resize() {
//得到扩容前的数组
Node<K,V>[] oldTab = table;
//得到扩容前数组的长度
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//获取到老的扩容阈值
int oldThr = threshold;
//定义新的数组容量,新的扩容阈值
int newCap, newThr = 0;
//老的数组容量大于0,有两种情况1、扩容过;2、创建Map指定了容量
if (oldCap > 0) {
//如果老的table容量大于等于最大值,放弃扩容,还是在老的table上增加元素
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//老数组长度*2 小于最大容量 并且 老的数组容量大于等于默认容量
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
//在老的扩容阈值上*2
newThr = oldThr << 1;
}
else if (oldThr > 0) // initial capacity was placed in threshold
//如果原数组的长度等于0 并且扩容阈值大于0,说明是通过new HashMap(n);创建,并且还是自一次添加元素
//这样的条件下table的长度直接等于老的扩容阈值就可以了
//一般指定table长度后 ++size不会大于threshold,不会触发扩容
newCap = oldThr;
else {
//如果扩容阈值=0,只有一种情况new HashMap(); 并且第一次添加元素
newCap = DEFAULT_INITIAL_CAPACITY;
//默认0.75 * 16 = 12,也就是table中元素个数存储到13的时候就需要进行扩容 if (++size > threshold)
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//通过new HashMap(n)创建的对象,第一次添加元素,上面只赋值了table的容量
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
//重新计算扩容阈值
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
//到这里新的数组容量和扩容阈值已计算完成 newCap、newThr
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;
//如果老数组中table[j]不为null,代表里面有元素
if ((e = oldTab[j]) != null) {
//清空引用,帮助GC
oldTab[j] = null;
//说明table[j]上只有一个元素;table[j]有四种情况,null、单个Node、Node链表、TreeNode红黑树
if (e.next == null)
//重新计算元素在newTab中的位置(要么是j要么是j+oldCap位置)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
//重新分配红黑树
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else {
//链表元素划分
//声明低位的的头节点和尾节点
Node<K,V> loHead = null, loTail = null;
//声明高位的头节点和尾节点
Node<K,V> hiHead = null, hiTail = null;
//声明循环的下一个节点
Node<K,V> next;
do {
next = e.next;
//这里只有两种结果0 | oldCap
if ((e.hash & oldCap) == 0) {
//如果尾节点为null,代表未进行赋值
if (loTail == null)
//低位的头节点为e
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;
//高位的链表放在j+oldCap的位置
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) 切分红黑树
//切分红黑树
//map当前map对象
//tab:map中的数组
//index:oldtable下标,需要切分的TreeNode
//bit:oldtable长度
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
//得到当前Tree
TreeNode<K,V> b = this;
//定义切分后高低位存储的tree,和链表切分规则类似
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) {
//从这里可以看出,TreeNode还维护了一个链表
next = (TreeNode<K,V>)e.next;
//解链
e.next = null;
//元素放在地位
if ((e.hash & bit) == 0) {
//从这里看出TreeNode不只是红黑树,还是一个双向链表
//因为尾节点始终赋值,这里可以先判断尾节点,第一个元素是,头结点和尾节点一致,
//这里不懂得可以看下LinkedList源码,底层也是这个逻辑
if ((e.prev = loTail) == null)
loHead = e;
else
//采用尾插法
loTail.next = e;
//把e赋值给尾节点
loTail = e;
//低位的元素个数+1
++lc;
}
else {
//放在高位
if ((e.prev = hiTail) == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
++hc;
}
}
if (loHead != null) {
//如果低位得长度小于等于6,进行转化成链表
if (lc <= UNTREEIFY_THRESHOLD)
//低位链化后赋值在table[index]位值
tab[index] = loHead.untreeify(map);
else {
//元素还是放在这个位置
tab[index] = loHead;
//不清楚这里为什么要满足高位必须要有元素 ???
if (hiHead != null)
//进行树化
loHead.treeify(tab);
}
}
if (hiHead != null) {
if (hc <= UNTREEIFY_THRESHOLD)
//高位链化后赋值在table[index + bit]位值
tab[index + bit] = hiHead.untreeify(map);
else {
tab[index + bit] = hiHead;
if (loHead != null)
hiHead.treeify(tab);
}
}
}
treeify(Node<K,V>[] tab)
//把链表进行树化
final void treeify(Node<K,V>[] tab) {
//定义红黑树根节点
TreeNode<K,V> root = null;
//this:需要树化的TreeNode双向链表,也是链表的头节点
for (TreeNode<K,V> x = this, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
//清空左右孩子节点引用
x.left = x.right = null;
if (root == null) {
x.parent = null;
//根节点为黑色节点
x.red = false;
root = x;
}
else {
//获取key
K k = x.key;
//获取hash
int h = x.hash;
//定义K的比较器
Class<?> kc = null;
for (TreeNode<K,V> p = root;;) {
int dir, ph;
K pk = p.key;
//父节点的hash值如果大于当前需要插入节点的hash值
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
//compareComparables(kc, k, pk) == 0 有两种情况1、parent节点key为null;2、将要插入的key == parentkey
//进入到这个方法有两层1、无比较器2、当前key和parentkey大小一致
//此方法只为了解决k==pk的问题
//先根据key的className对比,如果还是0,再根据内存地址对比
//咱们知道相同的key一定是相同的hashcode,它们equals值也相同,map中这种情况是一个最多是个替换操作
dir = tieBreakOrder(k, pk);
//上面这部分if else if 就是为了解决 k==pk = 0 的问题,并计算出 dir,dir大多情况是-1、1
//定义x节点的父节点
TreeNode<K,V> xp = p;
//给p变量赋值,为了更好向子节点查找
if ((p = (dir <= 0) ? p.left : p.right) == null) {
//指定x这个节点的父节点
x.parent = xp;
if (dir <= 0)
//如果小于等于0,把x放在p的左节点
xp.left = x;
else
//否则放在右节点
xp.right = x;
//对插入节点后的树进行平衡操作
//这里需要了解下二叉树(二叉树、二叉搜索树、平衡二叉树 基础),有兴趣可以看下
root = balanceInsertion(root, x);
break;
}
}
}
}
moveRootToFront(tab, root);
}
PS:今天内容就到这里了,如果有帮助到您,请关注下博主点个赞吧~~~