(一)hash算法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); // 这是key的hash算法,首先得到hashcode,然后hashcode右移16位得到高16位,接着进行异或操作,
// 使原来hashcode的低16位与右移后的高16位尽可能计算,减少hash冲突的概率,称之为扰动处理
}
(二)put存入数据
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) { // hashmap存入数据
Node<K,V>[] tab; Node<K,V> p; int n, i; // 初始化操作,tab初始是空的,p作为hash寻址的节点
if ((tab = table) == null || (n = tab.length) == 0) // 第一次插入数据,hashmap的数组是空的
n = (tab = resize()).length; // 数组是空的,所以扩容,并创建新的数组tab
if ((p = tab[i = (n - 1) & hash]) == null) // hash寻址算法,即对hash%n-1 取模计算得到所在位置的数组元素p,并判断该位置有没有填充数据
tab[i] = newNode(hash, key, value, null); // 如果该位置没有填充数据,就根据key value创建新的节点元素并放在该位置
else { // 数组不为空,hash寻址的位置已经有节点元素了
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))) // 节点元素p的hash值和传入的相同,节点元素p的key值也和传入的相同
e = p; // 将节点元素p 赋值给新的变量e,e代表要替换p的新节点,并在底下e.value = value做更新操作,即更新节点元素的值,返回节点的旧值
else if (p instanceof TreeNode) // 节点元素p是红黑树节点,即树的根节点root
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); // 将key value插入到红黑树中,可能是更新红黑树的旧节点的值,可能是增加新节点,并得到新的节点e
else { // 说明节点元素p不是红黑树节点,只有是链表结构上的节点
for (int binCount = 0; ; ++binCount) { // binCount代表在链表上寻址的次数,一条链表上的节点的数量<=7,如果>=8该链表就要转换为红黑树结构,所有节点转为TreeNode
if ((e = p.next) == null) { // 链表节点的next是空的,并且e代表链表的尾节点
p.next = newNode(hash, key, value, null); // 尾节点是空的,就创建新的节点并作为尾节点
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st // 一条链表上的插入节点的数量>=8
treeifyBin(tab, hash); // 链表转换为红黑树结构,所有节点转为TreeNode
break; // 跳出循环再链表上寻址
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) // 在链表上找到一个hash值相同、key也相同的节点,跳出循环后直接用传入的值更新旧节点的值
break;
p = e; // 说明本次循环既没有找到空的尾节点,也不是key相同的,则将e赋值给p,使p节点成为链表的下一个节点,跟e = p.next构成循环往后寻址,直到更新旧节点或插入新节点
}
}
if (e != null) { // existing mapping for key // e代表新的节点元素,可以是更新后的节点,也可以是新插入的节点
V oldValue = e.value; // 得到节点元素的旧值,对于更新的节点代表旧值,对于插入的节点代表新值
if (!onlyIfAbsent || oldValue == null)
e.value = value; // 用传入的值作为新节点的值
afterNodeAccess(e); // 这是linkedhashmap的扩展,如果设置访问顺序,则访问过的节点会移到链表的尾节点tail
return oldValue;
}
}
++modCount; // modCount代表修改的次数,每更新节点或插入新节点都代表一次修改
if (++size > threshold) // size代表节点数量,即节点数量超过阈值,就要扩容
resize(); // 扩大数组tab的容量,否则hash碰撞太频繁、插入的节点都最终挂载红黑树上导致树的层级过高,降低索引的效率
afterNodeInsertion(evict); // 这是linkedhashmap的扩展,根据移除条件,会将最老的节点即first节点移除,即将最老最少访问的节点移除
return null;
}
(三)扩容和rehash
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 (oldCap >= MAXIMUM_CAPACITY) { // 如果旧数组的数据量已经超过最大容量,无法再扩容直接return现有的数组
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY) // 旧数组的容量乘以2仍然小于最大容量,那么新数组的容量和阈值都扩大2倍
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold // 旧数组没有插入过数据,但在创建hashmap的时候初始化过阈值,则以阈值作为新的容量
newCap = oldThr;
else { // zero initial threshold signifies using defaults // 首次扩容,初始化数组的容量为16,阈值为16*0.75=12
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<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) { // 将数组的节点赋值给e 进行保存
oldTab[j] = null; // 释放数组节点的空间,以便被GC回收
if (e.next == null) // 节点e的next为空,说明数组的这个位置上是个单节点
newTab[e.hash & (newCap - 1)] = e; // 节点e用旧的hash值对新的容量取模,并保存到新数组中
else if (e instanceof TreeNode) // 节点e是红黑树节点的话,说明这个节点挂的是棵红黑树
((TreeNode<K,V>)e).split(this, newTab, j, oldCap); // 节点e作为根节点root,对红黑树进行拆分,分为两个更小的链表或红黑树
//将原来的红黑树更均匀的插入到新数组中并降低树的高度
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) { // 对旧容量取模为0的则作为低位链表
if (loTail == null)
loHead = e; // 循环第一个节点作为低位链表的头节点
else
loTail.next = e; // 有了头节点就开始链接下一个节点
loTail = e; // 循环过的节点作为尾节点,用于链接下一个节点
}
else { // 对旧容量取模不为0的则作为高位链表
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;
}
(四)红黑树rehash
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) { // 对红黑树进行拆分,分为两个更小的链表或红黑树
TreeNode<K,V> b = this; // 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指针对节点顺序遍历,而不是对左右子树进行遍历
next = (TreeNode<K,V>)e.next;
e.next = null;
if ((e.hash & bit) == 0) { // 对旧容量bit取模为0的节点则作为低位链表
if ((e.prev = loTail) == null)
loHead = e; // 循环第一个节点作为低位链表的头节点
else
loTail.next = e; // 后续遍历到的节点以链表的结构串联
loTail = e;
++lc;
}
else { // 对旧容量bit取模不为0的节点则作为高位链表
if ((e.prev = hiTail) == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
++hc;
}
}
if (loHead != null) {
if (lc <= UNTREEIFY_THRESHOLD) // 低位链表的节点数量<=6,不转为红黑树
tab[index] = loHead.untreeify(map); // 将链表存到跟旧数组下标index 相同位置的新数组中
else {
tab[index] = loHead; // 如果节点数量>6,并且高、低位链表都存在,则先将链表存到跟旧数组下标index 相同位置的新数组中,然后将链表转为红黑树
if (hiHead != null) // (else is already treeified)
loHead.treeify(tab);
}
}
if (hiHead != null) {
if (hc <= UNTREEIFY_THRESHOLD) // 高位链表的节点数量<=6,不转为红黑树
tab[index + bit] = hiHead.untreeify(map); // 将链表存到(旧数组下标index+旧容量)所在位置的新数组中
else {
tab[index + bit] = hiHead;
if (loHead != null)
hiHead.treeify(tab); // 高位链表转为红黑树
}
}
}
(扩展点)LRUMap超出缓存的大小会移除链表中first节点,即最老的元素,同时将访问过的节点移到tail节点,这样LRU可以保证最老最少访问的元素被移除掉