HashMap put 相关函数

文接上回,咱们讲了HashMap 的结构函数,首要便是设置 负载因子 和 扩容阈值。
这章咱们来看HashMap put 的相关函数,不多bb,上源码:

    public V put(K key, V value) {
return putValapple(hash(key), key, value, false, true);
}
@Override
public V putIfAbsent(K key, V value) {
return putVal(hash(key), key函数调用句子, value, true, true);
}
public void putAll(Map<? extends K, ? extends V> m) {
putMapEntries(m,函数调用c言语 true源码共享网);
}

put 相关的函数就这3 个,前两函数调用个都是直接调用的
putVal(hash(key), key, value, true, true),
后面一个应该还有形象,跟参数为Map结构函数调用的是同一个函函数调用进程数putMapEntries()。
接着咱们就看下putVal(h源码编辑器ash(key), k源码编辑器手机版下载ey, value, true, true) 这个函数

putVal(hash(key), key, value, true, true) 函数


transient Node<K,V>[] table;
/**
* 在put 相关办法中被调函数调用c言语用
*
* @param hash hash for key
* @param k函数调用c言语ey the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table函数调用c言语 i函数调用能够出现在表达式中吗s in creation mode.
* @return pjava编译器revious value, or null if nonejava怎样读
*/
final V pu函数调用tVal(int hash, K key, V value, boolean函数调用的三种方式 onlyIfAbsent,
boolean evict) {
Nodjava初学e<K,V>[]链表排序 tab; Node<K,V> p; int njava模拟器, i;
if ((tab = table) == null || (n = tab.length) == 0)//注释1
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash])application == nappearull) //注释2
tab[i] = new链表排序Node(h函数调用句子ash, kjava怎样读ey, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
els链表完结栈e if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {链表回转
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (bin链表不具有的特点是Cojava难学吗unt >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBinjavascript(tab, hash);
breaAPPk;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(函数调用的一般格局k))))
break;
p = e;
}
}
if (e != null) { // existing majava面试题pping for key
V oldValue = e.java编译器value;
if (!onlyIfAbapprovesent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;//注释3
if (++size > threshold)
resize();
afterNodeInserti链表的创建on(evict);
return njava面试题ull;
}
第一次调用时

假定咱们源码码头是通过HashMap()这个结构函数创立的HashMap 目标并第一次调用 put(K key, V value) 函数函数调用进程

  1. 咱们先看下No源码时代de 类的结构
    static class N链表c言语ode<K,V> implements Map.Entry<K,V&源码编辑器gt; {
final int hash;
fi链表的创建n源码时代al K key;
V value;
Nod函数调用c言语e&app装置下载lt;K,V> next;
Node(int hash, K key, V valu链表完结栈e, Node<K,V> next链表逆序) {
this.happstoreash = hash;
this.key = key;
this.val链表不具有的特点是ue = value;
this.next = next;
}
//省掉代码。。。
}

能够看出这是一个单链表结构,存放着 hash、key和vajava工作培训班l链表排序ue

  1. resize() 函数,作用:初始化或函数调用的一般格局者扩容表为原大小的2倍。源码后面再剖析。
  2. 知道以上的信息咱们在看 putVal() 函数的代码,注释1
if ((tab = table) == null || (n = tab.length) == 0)//注释1
n = (tab = resize()).length;

咱们知道DEFAULT_INITIAL_CAPACITY = 1 << 4 // aka 16 因此能够知道
tab = (Node<K,V>[])new Node[16] n = 16

  1. 接着往下看,注释2
if ((p = tab[i = (n - 1) &a函数调用进程mp; hashjava初学]) == null) //注释2
tab[i] = newNode(hash函数调用, key, value, nu链表排序ll)函数调用不能出现在以下哪种状况;

咱们是第一次调用,p = tab[i = (n - 1) & hash]肯定是null ,所以apple咱们这次就成功的把key,value 存到了tab[i] 中。

  1. 咱们走进了if,链表数据结构else 的代码就不必看了,直接到了//注释3 的方位 ++modCount 用于记录修改的次数,接着往函数调用c言语下看:
if (++size > threshold)
resize();

threshold函数调用时的实参和形参之间传递扩容阈值,初始化时为 DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR //aka 16*0.75 = 12, sizjava工作培训班e 为Ha源码编辑器手机版下载shM源码共享网ap 中保存 Node源码网站的数量,当等于 扩容阈值 时就需求对 tab 进行扩容。
接着往下是 afterNodeInsertion(evict);这是一个空办法,什么都没做。

void afterNodeInsertion(boolean evict) { }

好了,咱们第一次java难学吗调用就完毕了。成功的把数据存储到了HashMap 中,再回头看下咱们之前没有看的 else 中的状况

else 中的状况

tab[i = (n - 1) & ha函数调用不能出现在以下哪种状况sh]中已经有链表完结栈值的状况就会走到 else 中,看代码:

        static fin源码编程器al int TREEIFY_THRappointmentESHOLD = 8;
if ((p = tab[i = (n - 1) & hash]) =java怎样读= null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != nu函数调用能够作为一个函数的形参ll源码网站 && key链表.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreappointmenteNo函数调用句子de<K,V>)p).putTreeVal(this, tab, hash, key, v函数调用c言语alue);javascript
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next =链表完结栈 newNode(hash,approve key, value, null);
if (binCount >= TREEIFY_THRapproachESHOLD - 1) // -1 for 1st
treeif函数调用的三种方式yBin(tab, haAPPsh);
break;
}
if (e.hash == hash &&java言语amp;
((k = e.key) == key || (key != null && key函数调用中的参数太少.equals(k))))
b源码共享网reak;
p = e;
}
}
if (e != null) { //existing ma链表结构pping for key //注释4
V oldValuappstoree = e.v源码网alue;
if (!onlyIfAbsent || oldValue == null)
e.value = value源码编辑器手机版下载;
afterNodeAccess(e);
return oldValue;
}
}
  1. TreeNJavaode 为红黑树,有爱好的同学能够自行了解 红黑树深化剖析及Java完结

  2. 咱们看接下链表完结栈来的判别,能够分为3中状况

    • p.kjava难学吗ey 与 参数key 相同

    直接将p 赋值给 Node e

    • p 为 Tr链表结构eeNode

    Java需求保存的内容 增加到红黑树p 中,回来值赋给e

    • else

    遍里链表p,且成果赋值函数调用的一般格局给e; 也可分为3 中状况
    1)源码编辑器编程猫下载. if (e.hash =appstore= hash &&ampjava编译器; ((k = e.key) == key || (key != null && key.equals(k)源码网站))源码编辑器编程猫下载) 当链表中有Node 的key 与参数key 相一起,完毕遍历。
    2). if ((e = p.nex源码码头t) == null) 链表遍历完时
    将需求保存的内容 增加到链表完毕。假定链表长度小于8,完毕遍历
    3). 链表遍历完,且增加新增内容。链表长度大于等于8 时。
    实行 treeify函数调用Bin(tab, hash) 函数,然后完毕遍历。

    static final int MIN_Tjava环境变量装备REEIFY_CAPACITY = 64;
    final void treeifJavayBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<approveK,V> e;
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CA函数调用的一般格局PACITY)链表和数组的差异
    resize();
    elseAPP if ((e = tab[index = (n - 1) & hash]) != null链表和数组的差异) {
    TreeNode&l函数调用的三种方式t;K,V> hd = null, tl = null;
    do {
    TreeNode<K,V&g函数调用不能出现在以下哪种状况t; p = replacementTreeNode(e, null);
    if (tl == null)
    hd = p;
    else {
    p.prev = tl;
    tl.next = pappointment;
    }
    tl = p;
    } while ((e = e.next) != null);
    if (函数调用的三种方式(tab[index] = hd) != null)
    hd.treeify(tab);
    }
    }
    

    treeifyBin(tab, hash) 函数的逻辑是当tab 长度小于64 时就实行resize() 扩容,否则将链表转为红黑树

  3. 咱们会过来看注释函数调用c言语4 处代码

if (e != null)链表回转 { //当参数key 在tab 中有映射时
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}

onlyIfAbsent 为true 时不修改现有值
当参数key 在taapp装置下载b 中有映射时,根据条件掩盖现有值,并回来旧值。

int hash(Object key) 函数

static final int hash(Object key) {
int h;
re函数调用的三种方式turn (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  1. ^ 假定相对应位值相同函数调用时的实参和形参之间传递,则成果为0,否则为1, 假定相对应位都是1,则成果为1,否则为0
a     = 1010
b     = 0011
------------
a ^ b = 1源码码头001
a &链表结构amp;approach b = 0010
  1. (h = key.hashCode()) ^ (h >&g源码网站t;函数调用进程> 16) 这个办法最重要的一句代码。为什么要做 ^ (h &g函数调用的一般格局t;>> 16)这个操作呢?

是因为在putVal 函数中,是这样是运用的 tab[index = (n - 1) & hash],n 是表的长度,表的长度永远都是2函数调用时的实参和形参之间传递的幂次方,那么n-1的高位应该满是0,做 & 操作时会导致hash 的高位无法参与运算,从而会带来哈希抵触的风险。所以在核算key的哈希值的时分,做(h = key.hashCode()) ^ (h >>> 16)操作。函数调用时的实参和形参之间传递这也就让高位参与到tab[index = (n - 1) & hash]的核算中来了,即下降java环境变量装备了哈希抵触appreciate的风险又不会带来太大的功用问题。

resize() 函数

    final Node<K,V>[] resize() {
Node<K,V>[] oldTab = t源码码头able;
int oldCap = (oldTab == null) ? 0 : oldTab.l源码编辑器编程猫下载ength;源码资本
int oldThr = tjava模拟器hreshold;
in链表逆序t newCa源码资本p, newThr = 0;
if (oldCap > 0) {//table中已经有数据
if (ol函数调用的三种方式dCap >= MAXIMUM_CAPA链表不具有的特点是CITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// newCap源码网 = oldCap * 2
else if函数调用能够作为一个函数的形参 ((newCap = oldCap << 1) < MAXIMUM_C链表排序APACITY &&
oldCap >= DEFAULT_IN函数调用的一般格局ITIAL_CAPACITY)
newThr = oldThr << 1; //newThr = oldThr * 2 
}
else if函数调用进程 (oldThr > 0) //初始化是设置了容源码资本量和阈值 运用非空结构函数初始化
//回忆一下结构函数中 threshold = tableSizeFor(initialCapacity) 值为2的幂次方
//本来这源码个值其实是给newCap 所以需求为2的幂次方
// initial capacity was placed in threshold
newCap = oldThr;
else { //初始化是没有设置容量和阈值, 运用的java模拟器是空的结构函数初始化 zero initial threshold signifies usapp装置下载ing defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACI链表和数组的差异TY);
}
i链表回转f (newThr == 0) {// 上面判别进入 (ol函数调用时的实参和形参之间传递dThr > 0) 的状况,没有给newThr 赋值,
// 所以在这儿给newThr 赋值
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_appleCAPACITY &&链表amp; ft &函数调用的三种方式lt; (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshojava怎样读ld = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table =approve newTab;
if函数调用的一般格局 (oldTab != null) {
for (int j = 0; j < oldCap; ++jappearance) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
o链表数据结构ldTab[j] = njava面试题ull;
if (e函数调用.next == null)//链表只要一个节点的时分
newTab[e.hash & (newCap - 1)] = e;
else ijava怎样读f (e instanc源码编程器eof TreeNode)//节点为红黑树
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve orderappearance
Node<K,V> loHead = null, loTail = null;
Node<K,V&gjava环境变量装备t; hiHead = null, hiTail = null;
Node<K,V> next;
d链表排序o {
next = e.next;
if ((e.hash & oldCap)链表数据结构 == 0) {//注释函数调用能够出现在表达式中吗5
if (l链表逆序oTail == 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) {
loTail.next = null;
newT链表逆序ab[j] = loHead;//注释6
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiJavaHead;//注释7
}
}
}
}
}
return newTab;
}

r链表回转esize() 函数能够appear分为两个部分

  • 设置 newCap、newThr。剖析已经写在了代码中,逻辑便是:没有初始化的approve状况初始化、已经有函数调用中的参数太少值的乘以2
  • 创立newTab 并从头赋值。这儿咱们关键解说一下 if ((e.hash & ol源码码头dCap)链表c言语 == 0)注释5 处的代码。
  1. 首要咱们知道 tab[index = (n - 1) & hash],假定oldCap = 16 时
oldCap-1  = ... 0000 1111
hash1     = ... 0000 0101  -> ind链表完结栈ex1 = 0000 0101 = 5
hash2     = ... 0001 0101  -> index2 = 0000 0101 = 5
  1. 此刻需求扩容 newCap = oldCap*2 = 32
newCap-1  =函数调用能够作为一个函数的形参 ... 0001 1111
has源码共享网h1     = ... 0000 0101  -> index1 = 0000 0101 = 5
hash2Java     = ... 0001 0101  -> in函数调用不能出现在以下哪种状况dex2 = 0001 0101 = 5 + 16(oldCap)
  1. 从以上两步能够看出,在扩容的时分并不是一切index 都会改动的。而改动的关键便是在 hash 值在 oldCap 的位上是否为0。if ((e.java怎样读hash &am源码码头p; oldCap) == 0)注释5 处的代码就类似于一下列子。作用便是判别是否需求位移。
oldCap    = ... 0001 0000
hash1     = ... 0000 0101  -> 0
hash2     =java模拟器 ... 0001 0101  -> 1
  1. 这也解说了注释6、注释7处的代码

小结

这章咱们首链表完结栈要涉及到3个函数

  • putVal(…)

该函数担任数据刺进,当size 超越扩容阈值时调用resiappointmentze()函数扩容,当增加数据的链表长度大于等于8 时将链表转为红黑树。

  • hash(Obj函数调用句子ect key)

在核算ke链表y的哈希值的时分,用其本身hashcode值与其低16位做异或操作。这也就让高位参与到index的核算中来了,即appreciate下降了哈希抵触的风险又不会带来太大的功用问题。

  • resize()

从头设置table 的容量和扩容阈值,并新建ta链表c言语ble 把oldTable 的值填充进去。