目录
参考:
数据结构
数组 + 链表 + 红黑树
Node数组:
Node链表节点的数据结构: 是一个单链表;
红黑树的树节点数据结构:
根据key获取哈希桶数组索引位置?
hash()算法?
这里的Hash算法本质上就是三步:取key的hashCode值、高位运算、取模运算。
hash & (n - 1) : 按位与
put()方法的详细执行?
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)
//数组不为空,在相应的hash桶出无值,直接新建node写入
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))))
//判断了Hash桶相应位置是第一个值是否就是想要找的值,如果是,则直接替换即可
e = p;
else if (p instanceof TreeNode)
//说明发生了hash碰撞 , 先判断是否是一颗树
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//说明发生了hash碰撞 , 且不是红黑树,那就是单链表
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
//追加到链表尾部后, 需要判断是否超过了链表长度最大值8,如果超过,就要转化为红黑树
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;
}
get()方法的详细执行?
get() 、hash(key)
getNode()
/**
* Implements Map.get and related methods.
*
* @param hash hash for key
* @param key the key
* @return the node, or null if none
*/
final HashMap.Node<K,V> getNode(int hash, Object key) {
HashMap.Node<K,V>[] tab; //数组
HashMap.Node<K,V> first; // 赋值 first = tab[(n - 1) & hash]
HashMap.Node<K,V> e;
int n; // n = tab.length 这里n是table数组的长度
K k;
// 如果table数组为空,说明没有元素,直接返回null
// (n - 1) & hash 根据hash值和n-1按位与,计算数组下标位置,获取 元素节点first , 如果first == null, 说明没有元素,直接返回null
if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
// always check first node 先判断第一个节点是否是, 经过hash()函数计算得到的hash值虽然相同,但可能发生碰撞, 当 key 也相同时,说明找到了, 直接返回;
if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
//如果不是第一个节点, 则需要遍及链表或搜索红黑树
if (first instanceof HashMap.TreeNode)
//红黑树获取
return ((HashMap.TreeNode<K,V>)first).getTreeNode(hash, key);
do {
//遍历单链表 通过hash()函数计算的hash值相等且 key相同,则返回;
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
如何从红黑树查找节点呢?
getTreeNode(hash, key)
((TreeNode<K,V>)first).getTreeNode(hash, key); 使用头节点first获取
parent就是first节点的父节点,
如果parent ==null, 说明当前节点first就是红黑树的根节点; 不然 通过root()方法查找根节点;
reSize() 扩容?
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final int MAXIMUM_CAPACITY = 1 << 30; //默认最大容量, int类型的最大值是 1<<31=-2147483648, 1<<32 =1, 因此,左移31位已经是Int类型的数据能位移的最大值了
static final float DEFAULT_LOAD_FACTOR = 0.75f;
int threshold, loadFactor;
final HashMap.Node<K,V>[] resize() {
//老数组
HashMap.Node<K,V>[] oldTab = table;
//老数组长度
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//老数组扩容阈值
int oldThr = threshold;
int newCap = 0; //新数组容量
int newThr = 0; //新数组的扩容阈值 扩容时,不会重新指定loadFactor,使用原加载因子或默认加载因子
//(1) 有数组元素
if (oldCap > 0) {
//判断元素数量是否已到达上限
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//如果未到上限,判断扩容后是否会超上限
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
// 有符号左移一位
newThr = oldThr << 1; // double threshold
}
//(2)HashMap数组中无元素, 需要初始化, 初始化分为,指定了数组长度的初始化和未指定数组长度的初始化
//注意,此时oldThr,也即threshold, 存的并不是阈值,而是new HashMap指定的容量, oldThr > 0说明指定了容量, 不大于0,说明为指定,是0, 则会使用默认值
else if (oldThr > 0) // initial capacity was placed in threshold
// (2.1)指定了数组长度的初始化: HashMap的静态方法中,会将数组长度放入threshold字段上, 如果大于0, 说明初始了这个一个长度的数据map
newCap = oldThr;
else { // zero initial threshold signifies using defaults
//(2.2) 未指定数组长度的初始化,使用默认值, 初始容量 DEFAULT_INITIAL_CAPACITY=16, 初始阈值 = 初始容量(DEFAULT_INITIAL_CAPACITY) * 默认加载因子(DEFAULT_LOAD_FACTOR)
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
//未给定新数组长度扩容阈值时(2.1步骤没给赋值)
float ft = (float) newCap * loadFactor; //计算扩容阈值
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE);
}
threshold = newThr; //赋值扩容阈值
@SuppressWarnings({"rawtypes","unchecked"})
//new 了一个的数组, 此处才是数组初始化, 数组是懒加载的, 只有put方法执行后,才会触发这里的new
HashMap.Node<K,V>[] newTab = (HashMap.Node<K,V>[])new HashMap.Node[newCap];
table = newTab;
// 注意:当多线程场景下,获取数组长度、初始化新数组、多处判空等,都不是原子的,都有可能导致丢数据,线程不安全;
if (oldTab != null) {
//如果有元素,则进行扩容操作
for (int j = 0; j < oldCap; ++j) {
//遍历原数组
HashMap.Node<K,V> e;
// e = oldTab[j] 取出数组元素里的头元素
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
//说明次数只有一个元素, 不是链表,没有发生Hash碰撞, 则直接计算新数组下标并赋值,
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof HashMap.TreeNode)
//红黑树操作
((HashMap.TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
//链表时
HashMap.Node<K,V> loHead = null, loTail = null; // loHead 指向链表头部,loTail 指向链表尾部, loHead 和 loTail 可以确定一个链表;
HashMap.Node<K,V> hiHead = null, hiTail = null; // hiHead 指向链表头部,hiTail 指向链表尾部, hiHead 和 hiTail 可以确定一个链表;
HashMap.Node<K,V> next; //用于遍历链表时保存next节点
do {
next = e.next;
//(e.hash & oldCap) == 0 说明,该节点e再新数组下标的索引值 与 其在老数组下标的索引值相等
// 否则,该节点e再新数组下标的索引值 = 其在老数组下标的索引值 + 老数组长度
if ((e.hash & oldCap) == 0) {
//下面5行,就是构建链表了
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
// 下面5行,也是构建链表了
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null; //把链表尾部的元素节点的next值置空,防止还链接着其他节点
newTab[j] = loHead; //loHead 为头节点的新链表中的所有节点, 经过e.hash & oldCap == 0运算可知, 在新数组的下标位置还是j, 不需要移动
}
if (hiTail != null) {
hiTail.next = null;// //把链表尾部的元素节点的next值置空,防止还链接着其他节点
newTab[j + oldCap] = hiHead; // hiHead 为头节点的新链表中的所有节点, 经过e.hash & oldCap != 0运算可知, 在新数组的下标位置 是j + oldCap, 需要移动oldCap个位置
}
}
}
}
} else {
//没有值,说明不是扩容, 只是数组初始化
}
return newTab;
}
为什么最大容量是 1 << 30?
MAXIMUM_CAPACITY = 1 << 30;
首先,数组容量是Int类型, Int占 32位, 即 1 int = 4 byte = 32bit , 1byte = 8 bit, 即 1个字节占8位;
int的范围是 [ 2 ^-31 , 2^31 - 1] 即 [-2147483648, 2147483647];
从下图可知, 如果 1<< 31 时 超过了int的最大值, 变为负数, 因此不能继续向上扩容了 ;
1073741824 + 1073741824 = 2147483648, 已经比int的最大值 2147483647 还大1, 因此占位溢出了,变成了负数, 可见, 1 << 30 是能扩容的最大值;