之前看过一下其他的文章,有几篇写的比较好的,记得是美团的技术团队分享的,结合自己看源码做的笔记, 之前都只是作为自己的笔记存起来的,现在分享出来,可能有人会看,哈哈,太自恋了,笔记里面有些是其他 文章的分析,图是我自己整理的,要是有写分析借鉴了你的文章没注明出处,先道歉,因为隔的太久了,不记得是在哪看的文章啦。简书的富文本编辑不能像有道笔记那样可以显示颜色的,我就先截长图吧。
Jdk8版本的源码改动很多,还引入了红黑树的数据结构,具体几个方法还是看源码吧,因为是笔记,所以方法里面很多不重要的,比如抛异常的就没记到笔记,或者是用伪代码代替。
int DEFAULT_INITIAL_CAPACITY = 16 //初始容量
int MAXIMUM_CAPACITY = 1 << 30; //最大容量
float DEFAULT_LOAD_FACTOR = 0.75f; //负载因子
int TREEIFY_THRESHOLD = 8; //需要用树存储时的临界值
int UNTREEIFY_THRESHOLD = 6;
int MIN_TREEIFY_CAPACITY = 64; //树的最低的容量
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
// h = key.hashCode() 为第一步 取hashCode值
// h ^ (h >>> 16) 为第二步 高位参与运算
// h>>>16 是右移 16位, ^ 是异或运算, 只有 0 ^ 1 或者 1^0 才是1,其他为 0
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i; //p是计算出来的下标所在的结点, i 是插入下标, n 是 table 长度
if ((tab = table) == null || (n = tab.length) == 0) //table长度为 32
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
//这里是取模运算= hash % n
//key的哈希码与 n-1 相与, 得到下标, n-1 可以减少哈希冲突概率,因为2^n-1变成2进制会有很多个 1
// 如果是2^n变成二进制会有很多个 0, 0跟什么相与都是0,1跟 跟 0, 1相与会得到对应的结果
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; // 如果计算出来的数组下标已经有元素了, 直接用新的 value 替换该结点的 value
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else { //查询到链表的最后一个节点也没有找到,那么新建一个Node,然后加到最后一个元素的后面
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) { //找到的待插入结点 p 的后驱结点为空
p.next = newNode(hash, key, value, null); //将新的结点插入到 p.next, p是最后的结点
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 = e; //否则继续在第一个结点的后驱结点继续查找比较
}
}
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null) { e.value = value; }
afterNodeAccess(e);
// 如果计算出来的数组下标已经有元素了, 直接用新的 value 替换该结点的 value
//并返回旧的 value
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold; int newCap, newThr = 0;
if (oldCap > 0) { //将旧的容量翻倍, 旧的 临界值翻倍
if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) {
newThr = oldThr << 1; // double threshold
}
}
else if (oldThr > 0) { newCap = oldThr; } //旧的 临界值变为新的容量
else { // 设置默认容量, 临界值, 这里只出现在 第一次put就扩容的时候
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
threshold = newThr;
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab == null) {
return newTab;
}
// 这里的数组迁移比较消耗性能, 只需要把数组每个下标中有元素的结点直接放到新数组下标中,结点的引用链表还是没变
for (int j = 0; j < oldCap; ++j) { //将旧的哈希表复制到新的哈希表,oldCap是数组容量
if ( ( (Node<K,V>) e = oldTab[j] ) == null ) {
continue;
}
oldTab[j] = null;
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, loTail; hiHead, hiTail ; //all = null
Node<K,V> next;
do {
next = e.next;
// n=16 二进制为 0 0 0 1 0 0 0 0
// n=16 n-1=15 二进制为 0 0 0 0 1 1 1 1
// n=32 n-1=31 二进制为 0 0 0 1 1 1 1 1
//原来的下标为 5 二进制为 0 0 0 0 0 1 0 1
// 5&16=0,说明下标为5 的下划线高位是0,与新容量31相与还是5
// 所以只需要知道原下标的高位如果是0则使用原来的下标,是1则原下标+oldCap
if ((e.hash & oldCap) == 0) { //判断原下标的高位是否为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) { //原下标高位是 0 则使用原下标
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
return newTab;
}
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 { //将普通的链表结点 Node 转化为 树结点, 保存 hash, key, value
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);
//上面的循环目的是将 下标对应的链表的 Node 全部替换为 树结点 TreeNode
if ((tab[index] = hd) != null){
hd.treeify(tab); //将构造好的 TreeNode 链表转为红黑树, 提升查询时间复杂度
}
}
}
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))){
//先根据key算到 hash值,算出下标,如果下标的第一个结点的 hash值相等
//并且key相等就直接返回第一个 结点
return first;
}
if ((e = first.next) != null) {
if (first instanceof TreeNode) {
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
}
do { //否则逐个对比下标上的链表结点,相等条件为 hash, key相等
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
return e;
}
} while ((e = e.next) != null);
}
}
return null;
}