·HashMap相关参数:
局部变量:
存储数据的Node数组 table
阈值 Threshold
容量capacity
全局变量:
加载因子 final float loadFactor
阈值 int threshold
修改次数 int modCount
kv映射数量 transient int size
数据存储 transient Node<K,V>[] table
·new HashMap<>();
给负载因子赋初始值为0.75
/**
* 给负载因子赋初始值为0.75
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
static final float DEFAULT_LOAD_FACTOR = 0.75f;
·put(K key, V value)
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
hash运算:运算结果为:高16位不变,低16位为高16位与低16位进行异或运算(也叫扰动函数),所以如果高16位全0,则将原值原样输出,否则将异或运算后的结果输出。
public native int hashCode();
static final int hash(Object key) {
int h;
/**
* 按位异或运算(^):两个数转为二进制,然后从高位开始比较,如果相同则为0,不相同则为1。
* 扰动函数————(h = key.hashCode()) ^ (h >>> 16) 表示:
* 将key的哈希code一分为二。其中:
* 【高半区16位】数据不变。
* 【低半区16位】数据与高半区16位数据进行异或操作,以此来加大低位的随机性。
* 注意:如果key的哈希code小于等于16位,那么是没有任何影响的。只有大于16位,才会触发扰动函数的执行效果。
* */
// eg: 110100100110^000000000000=110100100110,由于k1的hashCode都是在低16位,所以原样返回3366
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
/**
* case1:
* h=高16位(全是0) and 低16位(有1)
* h >>> 16 = 低16位全部消失,那么变成了32位(全是0)
* h ^ (h >>> 16) = 原样输出
* case2:
* h=高16位(有1) and 低16位(有1)
* h >>> 16 = 低16位全部消失,那么变成了高16位(全是0)and低16位(有1)
* h ^ (h >>> 16) = 不是原样输出 将原高16位与原低16位进行扰动。
*/
}
key.hashCode():根据key的不同类型,在对应的类中有对应的实现,例如:key为int类型:
public static int hashCode(int value) {
return value;
}
String的hashCode():
public int hashCode() {
// 初始化 hash=0 h=0
int h = hash;
// value={'k','1'} value.length=2
// 只有第一次计算hash值时,才进入下面逻辑中。此后调用hashCode方法,都直接返回hash
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
// val[0]=107 val[1]=49
h = 31 * h + val[i];
}
// h=31(31*0+107)+49=3366
hash = h;
}
return h;
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K, V>[] tab;
Node<K, V> p;
int n, i;
// eg1: table=null
// eg2: table是长度为16的Node数组,且table[1]=Node(1, 1, "a1", null)
// eg3: table是长度为16的Node数组,且table[1]=Node(1, 1, "a1", null) ... table[6]=Node(6, 6, "a6", null)
// eg4: table是长度为16的Node数组,且table[1]=Node(1, 1, "a1", null) ... table[6]=Node(6, 6, "a6", null)
// eg6: table是长度为16的Node数组,且table[1]=Node(1, 1, "a1", null) ... table[6]=Node(6, 6, "a6", null)
// 如果是空的table,那么默认初始化一个长度为16的Node数组
if ((tab = table) == null || (n = tab.length) == 0) {
// eg1: resize返回(Node<K, V>[]) new Node[16],所以:tab=(Node<K, V>[]) new Node[16], n=16
n = (tab = resize()).length;
}
// eg1: i = (n-1)&hash = (16-1)&0 = 1111&0000 = 0000 = 0; 即:p=tab[0]=null
// eg2: i = (n-1)&hash = (16-1)&1 = 1111&0001 = 0001 = 1; 即:p=tab[1]=null
// eg3: i = (n-1)&hash = (16-1)&16 = 1111&10000 = 0000 = 0; 即:p=tab[0]=Node(0, 0, "a0", null)
// eg4: i = (n-1)&hash = (16-1)&32 = 1111&100000 = 0000 = 0; 即:p=tab[0]=Node(0, 0, "a0", null)
// eg6: i = (n-1)&hash = (16-1)&128 = 1111&10000000 = 0000 = 0; 即:p=tab[0]=Node(0, 0, "a0", null)
// 如果计算后的下标i,在tab数组中没有数据,那么则新增Node节点
if ((p = tab[i = (n - 1) & hash]) == null) {
// eg1: tab[0] = newNode(0, 0, "a0", null)
// eg2: tab[1] = newNode(1, 1, "a1", null)
tab[i] = newNode(hash, key, value, null);
} else { // 如果计算后的下标i,在tab数组中已存在数据,则执行以下逻辑
Node<K, V> e;
K k;
// eg3: p.hash==0, hash==16,所以返回false
// eg4: p.hash==0, hash==32,所以返回false
// eg6: p.hash==0, hash==128,所以返回false
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) { // 如果与已存在的Node是相同的key值
e = p;
}
// eg3: p instanceof Node,所以为false
// eg4: p instanceof Node,所以为false
// eg6: p instanceof Node,所以为false
else if (p instanceof TreeNode) { // 如果与已存在的Node是相同的key值,并且是树节点
e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
} else { // 如果与已存在的Node是相同的key值,并且是普通节点,则循环遍历链式Node,并对比hash和key,如果都不相同,则将新的Node拼装到链表的末尾。如果相同,则进行更新。
for (int binCount = 0; ; ++binCount) {
// eg3: p.next == null
// eg4-loop1: p.next == Node(16, 16, "a16", null) 不为空
// eg4-loop2: p.next == null
// 获得p节点的后置节点,赋值给e。直到遍历到横向链表的最后一个节点,即:该节点的next后置指针为null
if ((e = p.next) == null) {
// eg3: p.next = newNode(16, 16, "a16", null);
// eg4-loop2: p.next == newNode(32, 32, "a32", null);
// eg6: p.next == newNode(128, 128, "a128", null);
p.next = newNode(hash, key, value, null);
// eg3: binCount == 0
// eg4-loop2: binCount == 1
// binCount从0开始,如果Node链表大于8个Node,那么试图变为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) {
// eg6: tab={newNode(0, 0, "a0", [指向后面1个链表中的7个node]), newNode(1, 1, "a1", null)}, hash=128
treeifyBin(tab, hash);
}
// eg3: break
// eg4-loop2: break
break;
}
// eg4-loop1: e.hash==16 hash==32 所以返回false
// 针对链表中的每个节点,都来判断一下,是否待插入的key与已存在的链表节点相同,如果相同,则跳出循环,并在后续的操作中,将该节点内容更新为最新的插入值
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
break;
}
// eg4-loop1: p=e=Node(16, 16, "a16", null)
p = e;
}
}
// eg3: e = null
// eg4: e = null
// 如果存在相同的key值
if (e != null) {
// egx: String oldValue = "v1"
V oldValue = e.value;
// egx: onlyIfAbsent=false
if (!onlyIfAbsent || oldValue == null) {
// egx: e = Node(3366, "k1", "v2", null)
// 则将新的value值进行更新
e.value = value;
}
afterNodeAccess(e);
// egx: 返回oldValue="v1"
return oldValue;
}
}
// eg1: modCount==0 ++modCount==1
// eg2: modCount==1 ++modCount==2
// eg3: modCount==7 ++modCount==8
// eg4: modCount==8 ++modCount==9
++modCount;
// eg1: size=0, threshold=12
// eg2: size=1, threshold=12
// eg3: size=7, threshold=12
// eg4: size=8, threshold=12
if (++size > threshold) {
resize();
}
afterNodeInsertion(evict); // doing nothing
return null;
}
resize的条件:
1.数组为空第一次put的时候
2.当map中的元素超过阈值的时候
3. 在jdk1.8中,当某一数组的链表长度大于等于8时,尝试进行红黑树的转换,如果数组的长度小于64,此时进行resize而不是进行红黑树的转换;只有数组的长度大于等于64时,才转换成红黑树。
resize的流程图:
第一阶段:根据旧的capacity、threshold和table计算出新的capacity、threshold,第一次扩容,容量扩为16,后面扩容按照2倍扩容,如果old capacity已经大于等于integer的max_Value,将threshold设置为integer的Max_Value,返回原表

接上图:
第二阶段:根据新的capacity和threshold,构建出新的数组。

代码如下:
final Node<K, V>[] resize() {
// eg1: table=null
// eg6: table != null
Node<K, V>[] oldTab = table;
// eg1: oldCap=0
// eg6: oldCap=16
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// eg1: oldThr=threshold=0
// eg6: oldThr=threshold=12
int oldThr = threshold;
int newCap = 0;
int newThr = 0;
// 第一步:根据情况,调整新表的容量newCap和阈值newThr
if (oldCap > 0) {
// 如果老table的长度大于等于2^30(1 << 30) 1后面有30个0
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE; // 2^31-1 1后面有30个1
return oldTab;
}
// 如果将老Table的长度增长2倍作为新的容量长度(newCap),是否小于2^30(1 << 30) 并且 老Table长度是否大于等于16(1 << 4)
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) {
// eg6: newCap=32, newThr=24
newThr = oldThr << 1;
}
} else if (oldThr > 0) {
newCap = oldThr;
} else {
// eg1: oldCap=0 newCap=16 newThr=0.75f*16=12
newCap = DEFAULT_INITIAL_CAPACITY; // 默认【表容量】为16(1 << 4)
newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); // 默认【阈值因子】为0.75f
}
if (newThr == 0) {
float ft = (float) newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ? (int) ft : Integer.MAX_VALUE);
}
// eg1: threshold=newThr=12
// eg6: threshold=newThr=24
threshold = newThr;
// 第二步:根据newCap和newThr,构建新数组
// 初始化新表
@SuppressWarnings({"rawtypes", "unchecked"})
Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap];
// eg1: table=newTab=(Node<K, V>[]) new Node[16];
// eg6: table=newTab=(Node<K, V>[]) new Node[32];
table = newTab;
// eg1: oldTab=null
if (oldTab != null) { // 如果老的table里有数据,则进行数据迁移
// eg6: oldCap=16
// 循环纵向数组中的每个槽位Cap
for (int j = 0; j < oldCap; ++j) {
Node<K, V> e;
// eg6-loop1: j=0, e=oldTab[0]=Node(0, 0, "a0", nodeRef)
// eg6-loop2: j=1, e=oldTab[1]=Node(1, 1, "a1", null)
if ((e = oldTab[j]) != null) {
// eg6-loop1: oldTab[0] = null
// eg6-loop2: oldTab[1] = null
oldTab[j] = null;
// eg6-loop1: e.next=Node(16, 16, "a16", nodeRef)
// eg6-loop2: e.next==null
if (e.next == null) { // 没有后置节点,说明e是最后一个节点
// eg6-loop2: e.hash==1, newCap=32, 1&(32-1)==1 即:newTab[1]=Node(1, 1, "a1", null)
newTab[e.hash & (newCap - 1)] = e;
} else if (e instanceof TreeNode) { // e是树节点
((TreeNode<K, V>) e).split(this, newTab, j, oldCap);
} else {
Node<K, V> loHead = null;
Node<K, V> loTail = null;
Node<K, V> hiHead = null;
Node<K, V> hiTail = null;
Node<K, V> next;
do {
// eg6-loop1-loop1: next=e.next=Node(16, 16, "a16", nodeRef)
// eg6-loop1-loop2: next=e.next=Node(32, 32, "a32", nodeRef)
// eg6-loop1-loop3: next=e.next=Node(48, 48, "a48", nodeRef)
// eg6-loop1-loop4: next=e.next=Node(64, 64, "a64", nodeRef)
// eg6-loop1-loop5: next=e.next=Node(80, 80, "a80", nodeRef)
// eg6-loop1-loop6: next=e.next=Node(96, 96, "a96", nodeRef)
// eg6-loop1-loop7: next=e.next=Node(112, 112, "a112", nodeRef)
// eg6-loop1-loop8: next=e.next=Node(128, 128, "a128", nodeRef)
// eg6-loop1-loop9: next=e.next=null
next = e.next; // 获得oldTab数组下标的Node列表的下一个节点
// eg6-loop1-loop1: e.hash=0, oldCap=16, 00000000&10000==00000==0
// eg6-loop1-loop2: e.hash=16, oldCap=16, 00010000&10000==10000==16
// eg6-loop1-loop3: e.hash=32, oldCap=16, 00100000&10000==00000==0
// eg6-loop1-loop4: e.hash=48, oldCap=16, 00110000&10000==10000==16
// eg6-loop1-loop5: e.hash=64, oldCap=16, 01000000&10000==00000==0
// eg6-loop1-loop6: e.hash=80, oldCap=16, 01010000&10000==00000==16
// eg6-loop1-loop7: e.hash=96, oldCap=16, 01100000&10000==00000==0
// eg6-loop1-loop8: e.hash=112, oldCap=16, 01110000&10000==10000==16
// eg6-loop1-loop9: e.hash=128, oldCap=16, 10000000&10000==10000==0
if ((e.hash & oldCap) == 0) { // 计算e在老表oldTab的下标,如果是第一个Node,即:下标index==0
if (loTail == null) {
// eg6-loop1-loop1: loHead=e=Node(0, 0, "a0", nodeRef)
loHead = e; // 将loHead指向oldTab数组第一个下标的第一个元素e
} else {
// eg6-loop1-loop3: loTail.next=e=Node(32, 32, "a32", nodeRef)
// eg6-loop1-loop5: loTail.next=e=Node(64, 64, "a64", nodeRef)
// eg6-loop1-loop7: loTail.next=e=Node(96, 96, "a96", nodeRef)
// eg6-loop1-loop9: loTail.next=e=Node(128, 128, "a128", nodeRef)
loTail.next = e; //建立新的链表
}
// eg6-loop1-loop1: loTail=e=Node(0, 0, "a0", nodeRef)
// eg6-loop1-loop3: loTail=e=Node(32, 32, "a32", nodeRef)
// eg6-loop1-loop5: loTail=e=Node(64, 64, "a64", nodeRef)
// eg6-loop1-loop7: loTail=e=Node(96, 96, "a96", nodeRef)
// eg6-loop1-loop9: loTail=e=Node(128, 128, "a128", nodeRef)
loTail = e; // 将loTail指向oldTab数组第一个下标的最后一个元素e
} else { // 如果不是oldTab中的第一个下标Node
if (hiTail == null) {
// eg6-loop1-loop2: hiHead=e=Node(16, 16, "a16", nodeRef)
hiHead = e;
} else {
// eg6-loop1-loop4: hiTail.next=e=Node(48, 48, "a48", nodeRef)
// eg6-loop1-loop6: hiTail.next=e=Node(80, 80, "a80", nodeRef)
// eg6-loop1-loop8: hiTail.next=e=Node(112, 112, "a112", nodeRef)
hiTail.next = e; // 建立新的链表
}
// eg6-loop1-loop2: hiTail=e=Node(16, 16, "a16", nodeRef)
// eg6-loop1-loop4: hiTail=e=Node(48, 48, "a48", nodeRef)
// eg6-loop1-loop6: hiTail=e=Node(80, 80, "a80", nodeRef)
// eg6-loop1-loop8: hiTail=e=Node(112, 112, "a112", nodeRef)
hiTail = e;
}
}
// eg6-loop1-loop1: e=next=Node(16, 16, "a16", nodeRef)
// eg6-loop1-loop2: e=next=Node(32, 32, "a32", nodeRef)
// eg6-loop1-loop3: e=next=Node(48, 48, "a48", nodeRef)
// eg6-loop1-loop4: e=next=Node(64, 64, "a64", nodeRef)
// eg6-loop1-loop5: e=next=Node(80, 80, "a80", nodeRef)
// eg6-loop1-loop6: e=next=Node(96, 96, "a96", nodeRef)
// eg6-loop1-loop7: e=next=Node(112, 112, "a112", nodeRef)
// eg6-loop1-loop8: e=next=Node(128, 128, "a128", nodeRef)
// eg6-loop1-loop9: e=next=null
while ((e = next) != null); // do-while
// eg6-loop1: loTail=Node(128, 128, "a128", null)
if (loTail != null) {
loTail.next = null;
// eg6-loop1: j=0, newTab[0]=loHead=Node(0, 0, "a0", nodeRef)
newTab[j] = loHead;
}
// eg6-loop1: loTail=Node(112, 112, "a112", nodeRef)
if (hiTail != null) {
// eg6-loop1: loTail=Node(112, 112, "a112", null)
hiTail.next = null;
// eg6-loop1: j=0, oldCap=16, newTab[16]=hiHead=Node(16, 16, "a16", nodeRef)
newTab[j + oldCap] = hiHead;
}
}
}
}
}
// eg1: newTab = (Node<K, V>[]) new Node[16]
// eg6: newTab = (Node<K, V>[]) new Node[32]
return newTab;
}
注:进行resize和put时,会进行当前元素的位置的计算,比如第一次插入时,初始化容量为16,key为0,此时hash之后的值也是0,根据槽位的计算公式:hash(key)&(capacity-1) (-1是为了保证低位对&运算无影响)计算得槽位为0,key为16、32时计算结果与0相同,都在0位,所以直接接在Node0之后(jdk1.8及之后是尾插法,所以接在后面),而当扩容的时候,此时的capacity为32,再次根据公式进行计算,得到的结果是0-0,16-16,32-0,这就表示,Node0放在0号位,Node16放到16号位,Node32放到0号位,接在Node0之后。
·get(Object key)
public V get(Object key) {
Node<K, V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
本文详细剖析了HashMap的内部工作原理,包括初始化、put操作、哈希计算以及resize过程。讲解了负载因子、阈值、容量等关键参数的作用,特别是resize时如何根据旧容量和新容量进行数据迁移,以及链表到红黑树的转换条件。内容涵盖HashMap的存储结构、哈希冲突解决和扩容策略,对于理解HashMap的高效性能有重要帮助。
1184

被折叠的 条评论
为什么被折叠?



