Java HashMap的put方法

// 当map中通过put传入值得时候
HashMap<String, String> map = new HashMap<>();
map.put(“1”, “1”);

//源码中调用了改方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

// 在public HashMap(int initialCapacity, float loadFactor)和public HashMap(int initialCapacity)方法中调用了该方法tableSizeFor(int cap) 对hashMap的threshold(阀值)进行了一些优化,如果传进来的initialCapacity是15其实内部并不是初始化长度为15的table,以下是源码:
static final int tableSizeFor(int cap) {
// 在这一步中 直接将数组的阀值设置成2的n次幂,为了hash & (n - 1) 更好的运算
// https://blog.csdn.net/fan2012huan/article/details/51097331
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

//下面是put方法方法中调用的方法putVal()方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
/**
* Node<K,V>: 是实现了Map.Entry<K,V>的接口,来实现Map中的链表,
* table: Node<K,V>[]是存储链表的数组
* */
Node<K, V>[] tab; // 初始化数组
Node<K, V> p; //初始化链表
int n, i;
if ((tab = table) == null || (n = tab.length) == 0) {
// 这里是调用HashMap()的时候,当table是空的时候,初始化长度为16的数组
// n:等于16
n = (tab = resize()).length;
}
// 当 lenth = 2的n次方的時候,X % length = X & (length - 1),所以length的初始长度是1 << 4,为了做到2的N次方
// 这里是取余数填进数组中去,如果中没有数据就直接插进去
// 通过hash寻址到了p这个节点,p是数组中的起始节点,后面需要通过轮询来讲put进来的数据插入到尾部
if ((p = tab[i = (n - 1) & hash]) == null)
// 该table中没有node节点,直接往table里面插入node节点
tab[i] = newNode(hash, key, value, null);
else {
// 当数组中已经有了Node节点,
Node<K, V> e;
K k;
// 判断p节点中的hash值 == put进去的值的hash是否相同
// 判断key值是否相同如果相同
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
// e就是p的下一个节点
if ((e = p.next) == null) {
// 如果末尾节点,直接采用尾插法,直接将put进来的node节点插入到尾部
p.next = newNode(hash, key, value, null);
// 如果链表长度大于8,就直接转换成红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 如果发现key值重复了,也就是要插入的key已经存在,那么直接break,结束遍历(不用再费劲去插入了)
// 到后面的if(e != null) 那一步将value修改就好了
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
// 将e复制给p,轮训链表,直到 (e = p.next) == null 满足条件,将put进来的node节点插入到尾部,break跳出循环
p = e;
}
}
//修改key相同的时候,不用直接插入,修改value值就好了
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
// 将传进来的值,复制给key相同的节点
e.value = value;
afterNodeAccess(e);
// 当key值相同的时候,返回原来的值
return oldValue;
}
}
// 用于累计hashMap的修改总数
++modCount;
// 如果size大于初始值,就需要开始扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

//下面是扩容代码
final Node<K, V>[] resize() {
/**
* oldCap:是老数组的长度
* newCap:是新数组的长度
* oldTab:是老数组
* newTab:是新数组
* oldThr:是老的临界值
* newThr:是新的临界值
/
// 创建一个oldTab数组用于保存之前的数组
Node<K, V>[] oldTab = table;
// 获取原来是数组的长度
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// threshold表示当HashMap的size大于threshold时会执行resize操作。原来数组扩容的临界值
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
// 桶数组table已经被初始化
// 如果原来的数组长度大于最大值(2^30)
if (oldCap >= MAXIMUM_CAPACITY) {
// 扩容临界值提高到正无穷
threshold = Integer.MAX_VALUE;
// 返回原来的数组,也就是系统已经管不了了,已经无法在进行扩容
return oldTab;
}
// else if((新数组newCap)原来数组的长度乘2) < 最大值(2^30) && (原来的数组长度)>= 初始长度(2^4))
// 这个else if 中实际上就是咋判断新数组(此时刚创建还为空)和老数组的长度合法性,同时交代了,
// 我们扩容是以2^1 为单位扩容的。下面的newThr(新数组的扩容临界值)一样,在原有临界值的基础上扩 2^1
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
// 为了知道老数组的长度是否合法
// 临界值扩容至原来的2倍
newThr = oldThr << 1; // double threshold
} else if (oldThr > 0) // initial capacity was placed in threshold
// 这个时候 threshold > 0,调用 HashMap(int) 和 HashMap(int, float) 构造方法时会产生这种情况,
// 此种情况下 newCap = oldThr,newThr 在第二个条件分支中算出
newCap = oldThr;
else { // zero initial threshold signifies using defaults
// threshold为0的时候 使用初始的阀值,(刚初始化map的时候,threshold为0)
// 数组长度设置成16
newCap = DEFAULT_INITIAL_CAPACITY;
// 计算默认容量下的阀值 值为12 ,当size大于12的时候,就要开始进行扩容了
newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
// 当oldThr > 0的时候 newThr的值被初始化了为0,这里要重新设置阀值,此时newCap = oldThr
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) {
// 遍历原来的table
for (int j = 0; j < oldCap; ++j) {
Node<K, V> e;
// 逐个获取每个数组中的所有节点
if ((e = oldTab[j]) != null) {
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节点
Node<K, V> loHead = null, loTail = null;
Node<K, V> hiHead = null, hiTail = null;
// 用于进行链表的轮询
Node<K, V> next;
do {
// 开始遍历该table位置中的所有node节点,这些node的hash值都是相同的
// 获取下一个节点的数据,用来进行循环,遍历每一个node节点
next = e.next;
// 这里为什么是e.hash & oldCap: 为什么不是 e.hash & (oldCap - 1)
/
我们假设 oldCap = 16, 即 2^4, 16 - 1 = 15, 二进制表示为 0000 0000 0000 0000 0000 0000 0000 1111
可见除了低4位, 其他位置都是0(简洁起见,高位的0后面就不写了), 则 (16-1) & hash 自然就是取hash值的低4位,我们假设它为 abcd.
以此类推, 当我们将oldCap扩大两倍后, 新的index的位置就变成了 (32-1) & hash, 其实就是取 hash值的低5位.
那么对于同一个Node, 低5位的值无外乎下面两种情况:
0abcd
1abcd
其中, 0abcd与原来的index值一致, 而1abcd = 0abcd + 10000 = 0abcd + oldCap
故虽然数组大小扩大了一倍,但是同一个key在新旧table中对应的index却存在一定联系: 要么一致,要么相差一个 oldCap。
而新旧index是否一致就体现在hash值的第4位(我们把最低为称作第0位), 怎么拿到这一位的值呢, 只要:
hash & 0000 0000 0000 0000 0000 0000 0001 0000
上式就等效于
hash & oldCap
故得出结论:
如果 (e.hash & oldCap) == 0 则该节点在新表的下标位置与旧表一致都为 j
如果 (e.hash & oldCap) == 1 则该节点在新表的下标位置 j + oldCap
根据这个条件, 我们将原位置的链表拆分成两个链表, 然后一次性将整个链表放到新的Table对应的位置上*/
if ((e.hash & oldCap) == 0) { //e.hash & oldCap 要么等于0,要么等于1
// 通过计算e.hash&oldCap0构造一条链
if (loTail == null)
loHead = e; // 插入表头
else
// 这2句代码有点看不懂
loTail.next = e;
loTail = e;
} else {
// 通过e.hash&oldCap!=0构造另外一条链
if (hiTail == null)
hiHead = e; // 插入表头
else
hiTail.next = e;
hiTail = e;
}
// 如果还有下一个节点接着操作
} while ((e = next) != null);
// 遍历结束以后,将tail指针指向null
// e.hash&oldCap
0构造而来的链的位置不变
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
// e.hash&oldCap!=0构造而来的链的位置在数组j+oldCap位置处
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值