本文会通过HashMap中的put方法为入口,进行源码解读,文章较长,需要耐心阅读

说明

/** */: 代表注释,区别于例子注释
egx::代表例子注释

解读前须知

源码主要关注4⼤点:

  1. 确定哈希桶数组索引的位置
    HashMap.hash() ——> hashCode()
  2. 插⼊数据
    HashMap.put()
  3. 扩容机制
    resize()
  4. 红⿊树 ⾮常复杂,⼤家⾃学吧
    treeify()

解读是通过HashMap的put方法为入口进行分析,其中eg注释的都是测试例子,具体测试代码如下:

@Test
public void testResize(){HashMap hashMap = new HashMap();// 下标0和1插入NodehashMap.put(0, "a0");  // 00000000hashMap.put(1, "a1");  // 00000001// 下标0,横向插入多个Node形成链表hashMap.put(16, "a16");  // 00010000hashMap.put(32, "a32");  // 00100000hashMap.put(48, "a48");  // 00110000hashMap.put(64, "a64");  // 01000000hashMap.put(80, "a80");  // 01010000hashMap.put(96, "a96");  // 01100000hashMap.put(112, "a112");  // 01110000hashMap.put(128, "a128");  // 10000000// hashMap.put(144, "a144");  // 10010000
}

源码解读

解读之前,需要先了解下几个字段作用

 /*** 默认的初始容量*/static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16/*** 最大容量,之后会看到很多次*/static final int MAXIMUM_CAPACITY = 1 << 30;/*** 默认的加载因子,构造函数可以指定*/static final float DEFAULT_LOAD_FACTOR = 0.75f;/*** 树化阈值,默认达到8个节点树化*/static final int TREEIFY_THRESHOLD = 8;/*** 被treeified的最小表容量,否则仅仅resize。* TREEIFY_THRESHOLD至少为4,以避免大小调整和treeification阈值之间的冲突。*/static final int MIN_TREEIFY_CAPACITY = 64;

先来看下put方法

    public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}

put方法很简单,但注意,这里有一个hash的方法,进入hash方法

    static final int hash(Object key) {int h;/*** 按位异或运算(^):两个数转为二进制,然后从高位开始比较,如果相同则为0,不相同为1** 扰动函数:(h = key.hashCode()) ^ (h >>> 16)*      将key的hashCOde一分为二*      【高半区16位】数据位不变*      【低半区16位】数据与高半区16位数据进行异或操作,以此来加大低位的随机性* 【注意】如果key的哈希code小于等于16位,那么是没有任何影响的。只有大于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位进行扰动。*/// eg1: 110100100110^000000000000=110100100110,由于k1的hashCode都是在低16位,所以原样返回3366,没有扰动效果return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}

这里其实就是利用高16位+低16位进行一个扰动,使得获取比较hash时,高16位也能参与hash,具体可以来看一张图比较明了

图中可以看到,h>16位的,则高16位与低16位进行异或,达到扰动效果,如果h<=16位的,则保持原样,没有扰动效果
put方法中除了hash,主要还是调用putVal进行实现,putVal方法如下

    /*** Implements Map.put and related methods.** @param hash         key的hash值* @param key          key值* @param value        value值* @param onlyIfAbsent 如果是true,则不改变已存在的值* @param evict        如果为false,则表为创建模式* @return previous value, or null if none*/// eg1: hash=0 key=0 value="a0" onlyIfAbsent=false evict=true// eg2: hash=1 key=1 value="a1" onlyIfAbsent=false evict=true// eg3: hash=16 key=16 value="a16" onlyIfAbsent=false evict=true// eg4: hash=32 key=32 value="a32" onlyIfAbsent=false evict=true// eg5: 由于执行步骤与eg4相似,故略过。// eg6: hash=128 key=128 value="a128" onlyIfAbsent=false evict=truefinal 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) {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);}/** 如果下标i计算后,在tab数组中已存在,则执行以下逻辑 */else {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/** 如果与已存在的Node是相同的key值 */if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) {e = p;}// eg3: p instanceof Node,所以为false// eg4: p instanceof Node,所以为false// eg6: p instanceof Node,所以为false/** 如果已存在的Node是树节点 */else if (p instanceof TreeNode) {e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);}/** 如果已存在的节点key不同且是普通节点,则循环遍历链式Node,并对比hash何key,如果都不相同,则将新的Node拼装到链表的末尾。如果相同则进行更新 */else {for (int binCount = 0; ; ++binCount) {// eg3: p.next == null// eg4-loop1: p.next == Node(16, 16, "a16", null) 不为空// eg4-loop2: p.next == nullif ((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/** 如果链表大于8个Node,那么变为红黑树 */if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st{// eg6: tab={newNode(0, 0, "a0", [指向后面1个链表中的7个node]), newNode(1, 1, "a1", null)}, hash=128/** 将链表变为红黑树 */treeifyBin(tab, hash);}// eg3: break// eg4-loop2: breakbreak;}// eg4-loop1: e.hash==16 hash==32 所以返回falseif (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/** 以上的多个判断e不等于null,则表示存在相同的key值 */if (e != null) { // existing mapping for key// egx: String oldValue = "v1"V oldValue = e.value;// egx: onlyIfAbsent=falseif (!onlyIfAbsent || oldValue == null) {// egx: e = Node(3366, "k1", "v2", null)/** 则将Node(e)新的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/** size+1>threshold,说明需要扩容了 */if (++size > threshold) {resize();}afterNodeInsertion(evict);return null;}

这个方法包含了主要的逻辑,具体功能可以详细看注释,其中有两个地方执行了resize()方法,一个是table为空的时候;一个是size达到了阈值,需要进行扩容,我们来看看reseze()方法

    /*** Initializes or doubles table size.  If null, allocates in* accord with initial capacity target held in field threshold.* Otherwise, because we are using power-of-two expansion, the* elements from each bin must either stay at same index, or move* with a power of two offset in the new table.** @return the table* <p>* 【重要】table扩容* <p>* 常量名词解释* Tab->Table 表* Cap->Capacity 容量* Thr->Threshold 阈值*/final Node<K, V>[] resize() {// eg1: table=null// eg6: table != nullNode<K, V>[] oldTab = table;// eg1: oldCap=0// eg6: oldCap=16int oldCap = (oldTab == null) ? 0 : oldTab.length;// eg1: oldThr=threshold=0// eg6: oldThr=threshold=12int oldThr = threshold;int newCap, newThr = 0;/** 第一步:根据情况调整新表的容量newCap和阈值newThr */if (oldCap > 0) {/** 如果老的table的长度大于等于2^30(1 << 30) */if (oldCap >= MAXIMUM_CAPACITY) {/** threshold=2^31-1 */threshold = Integer.MAX_VALUE;return oldTab;}/** 如果将老的容量oldCap*2做为新的容量newCap后还是小于2^30并且老的容量大于16(1<<4) */else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) {// eg6: newCap=32, newThr=24/** newThr=oldThr*2 */newThr = oldThr << 1;}}// 初始容量设为阈值else if (oldThr > 0) {newCap = oldThr;} else {               // zero initial threshold signifies using defaults// eg1: oldCap=0 newCap=16 newThr=0.75f*16=12newCap = 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);}// eg1: threshold=newThr=12// eg6: threshold=newThr=24threshold = 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/** 如果老的oldTab里面有数据,则进行数据迁移到新的newTab */if (oldTab != null) {// eg6: oldCap=16for (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/** 将j位置的Node置空 */oldTab[j] = null;// eg6-loop1: e.next=Node(16, 16, "a16", nodeRef)// eg6-loop2: e.next==null/** 如果没有后置节点,说明e是最后一个节点 */if (e.next == null) {// eg6-loop2: e.hash==1, newCap=32, 1&(32-1)==1 即:newTab[1]=Node(1, 1, "a1", null)newTab[e.hash & (newCap - 1)] = e;/** e是树结点 */} else if (e instanceof TreeNode) {((TreeNode<K, V>) e).split(this, newTab, j, oldCap);} else { // preserve orderNode<K, V> loHead = null, loTail = null;Node<K, V> hiHead = null, 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/** 获取oldTable数组下标的下一个节点 */next = e.next;// 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/** 计算e在老表oldTable的下标,如果是第一个Node,即下标index==0 */if ((e.hash & oldCap) == 0) {if (loTail == null) {// eg6-loop1-loop1: loHead=e=Node(0, 0, "a0", nodeRef)/** 将loHead指向oldTable数组的第一个元素e */loHead = 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指向oldTable中的第一个元素e */loTail = e;} else {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);// 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;}

这里主要有如下几种情况:

  1. 老的容量大于0,且老的容量大于2^30 ,则设置新的阈值为,2^31-1
  2. 老的容量大于0,且老的容量2大于2^30且老的容量大于默认初始化容量16,则新的阈值为老的容量2
  3. 老的阈值大于0,新容量=老的阈值大小
  4. 其它情况,新容量等于默认初始化容量16,新阈值等于默认的加载因子(DEFAULT_LOAD_FACTOR)*默认初始化容量(DEFAULT_INITIAL_CAPACITY)

接下来就是对确定好的阈值和容量进行构建新表,遍历oldTab的时候,执行逻辑有如下几种情况:

  1. 如果是叶子节点,直接将e赋值给newTab
  2. 如果是树节点(TreeNode),调用split方法
  3. 其它情况,构建链表,并将表头赋值给newTab

这里有一个关键方法,就是split,这个方法是在TreeNode私有类中,方法主要作用是将tree bins中的节点分成上下两个tree bins,如果tab太小就取消split,仅调整大小,我们来详细看下:

/*** 将tree bins中的节点分成上下两个tree bins,如果tab太小就取消split,仅调整大小** @param map   the map* @param tab   the table for recording bin heads* @param index the index of the table being split* @param bit   the bit of hash to split on*/final void split(HashMap<K, V> map, Node<K, V>[] tab, int index, int bit) {// 做个赋值,因为这里是((TreeNode<K,V>)e)这个对象调用split()方法,所以this就是指(TreeNode<K,V>)e对象,所以才能类型对应赋值TreeNode<K, V> b = this;// 设置低位首节点和低位尾节点TreeNode<K, V> loHead = null, loTail = null;// 设置高位首节点和高位尾节点TreeNode<K, V> hiHead = null, hiTail = null;// 定义两个变量lc和hc,初始值为0,后面比较要用,他们的大小决定了红黑树是否要转回链表int lc = 0, hc = 0;// 这个for循环就是对从e节点开始对整个红黑树做遍历for (TreeNode<K, V> e = b, next; e != null; e = next) {// 取e的下一节点赋值给next遍历next = (TreeNode<K, V>) e.next;// 取好e的下一节点后,把它赋值为空,方便GC回收e.next = null;// 以下的操作就是做个按位与运算,按照结果拉出两条链表if ((e.hash & bit) == 0) {if ((e.prev = loTail) == null) {loHead = e;} else {loTail.next = e;}loTail = e;// 做个计数,看下拉出低位链表下会有几个元素++lc;} else {if ((e.prev = hiTail) == null) {hiHead = e;} else {hiTail.next = e;}hiTail = e;// 做个计数,看下拉出高位链表下会有几个元素++hc;}}// 如果低位链表首节点不为null,说明有这个链表存在if (loHead != null) {// 如果链表下的元素小于等于6if (lc <= UNTREEIFY_THRESHOLD) {// 那就从红黑树转链表了,低位链表,迁移到新数组中下标不变,还是等于原数组到下标tab[index] = loHead.untreeify(map);} else {// 低位链表,迁移到新数组中下标不变,还是等于原数组到下标,把低位链表整个拉到这个下标下,做个赋值tab[index] = loHead;// 如果高位首节点不为空,说明原来的红黑树已经被拆分成两个链表了if (hiHead != null) {//那么就需要构建新的红黑树了loHead.treeify(tab);}}}// 如果高位链表首节点不为null,说明有这个链表存在if (hiHead != null) {// 如果链表下的元素小于等于6if (hc <= UNTREEIFY_THRESHOLD) {// 那就从红黑树转链表了,高位链表,迁移到新数组中的下标=【旧数组+旧数组长度】tab[index + bit] = hiHead.untreeify(map);} else {// 高位链表,迁移到新数组中的下标=【旧数组+旧数组长度】,把高位链表整个拉到这个新下标下,做赋值tab[index + bit] = hiHead;// 如果低位首节点不为空,说明原来的红黑树已经被拆分成两个链表了if (loHead != null) {// 那么就需要构建新的红黑树了hiHead.treeify(tab);}}}}

就是将一个链表拆分为两个链表,完成拆分后判断每个链表的元素个数,情况如下:

  1. 个数小于等于6(UNTREEIFY_THRESHOLD),则将红黑树转链表
  2. 个数大于6,且头节点不为空,则将链表转红黑树

为了便于大家理解,我们通过一张图来理解以上的split的方法:

这个方法中有涉及到红黑树转换,我们来了解下treeifyBin方法:

在之前putVal方法中,也有进行转红黑树的操作,调用方法为treeifyBin,和treeify的区别主要是增加了条件判断,比如表长度是否满足最小数容量的要求,如果不是仅仅做扩容操作resize,我们来看看具体实现:

/*** 依据给定的hash替换此节点为树形节点,并树形化。如果表太小则是调整容量resize。*/// eg6: hash=128// tab[0]=Node(0, 0, "a0", nodeRef)——>Node(16, 16, "a16", nodeRef)——>Node(32, 32, "a32", nodeRef)——>Node(48, 48, "a48",nodeRef)——>Node(64, 64, "a64", node)——>Node(80, 80, "a80", node)——>Node(96, 96, "a96", node)——>Node(112, 112, "a112", node)// tab[1]=Node(1, 1, "a1", null)final void treeifyBin(Node<K, V>[] tab, int hash) {int n, index;Node<K, V> e;//  eg6: tab !=null, tab.length=16/** 当tab的长度小于64,则不转换为数,只扩展数组大小 */if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) {//  eg6: 执行resize()resize();/** 如果新增的node要插入的数组位置已经有node存在,则取出该位置的node为e */} else if ((e = tab[index = (n - 1) & hash]) != null) {/** 头节点 */TreeNode<K, V> hd = null;/** 尾节点 */TreeNode<K, V> tl = null;do {/** 将Node转换为TreeNode->new TreeNode<>(p.hash, p.key, p.value, next); */TreeNode<K, V> p = replacementTreeNode(e, null);/** 将每个Node转换尾TreeNode,并且用prev和next指针进行链接,并以hd为头节点 */if (tl == null) {hd = p;} else {p.prev = tl;tl.next = p;}tl = p;} while ((e = e.next) != null);/** 如果为头节点的hd不为空,则进行数形化操作 */if ((tab[index] = hd) != null) {hd.treeify(tab);}}}

满足树形化条件则调用了treeify方法,至此所有的执行操作基本了解了

总结

HashMap容器逻辑还是比较复杂的,大家可以针对注释和eg例子,再配合流程图,就能够很好的理解HashMap的put过程,至于其它逻辑比较简单,就没有详细说明
使用流程图来看看整体的put操作:

如果喜欢我的文章记得一定要一键三连哟(别下次一定!!!)

HashMap 实现原理及源码解析(jdk8 底层⽤的是数组+链表/红⿊树)相关推荐

  1. ArrayList 实现原理及源码解析(jdk8 底层⽤的是数组)

    本文会主要从集合扩容.添加元素.删除元素三个方面来进行源码解读 在开始解读之前,我们先来看一下ArrayList添加一个元素的流程 基本在图中已经比较全面的说明了add一个元素的全流程 源码解读 我们 ...

  2. LinkedList 实现原理及源码解析(jdk8 底层⽤的是链表)

    LinkList顾名思义,就是通过链表实现的集合,优点就是插入.删除速度快,但遍历速度不及ArrayList,本质原因还是数据结构的不同,至于ListList和ArrayList的优缺点网上很多,这里 ...

  3. hashmap删除指定key_Java集合之HashMap源码解析(JDK8)

    哈希表(hash table)也叫散列表,是一种非常重要的数据结构,应用场景非常丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,而HashMap的实现原理也常常出 ...

  4. 【特征匹配】ORB原理与源码解析

    相关 : Fast原理与源码解析 Brief描述子原理与源码解析 Harris原理与源码解析 http://blog.csdn.net/luoshixian099/article/details/48 ...

  5. Redis进阶- Redisson分布式锁实现原理及源码解析

    文章目录 Pre 用法 Redisson分布式锁实现原理 Redisson分布式锁源码分析 redisson.getLock(lockKey) 的逻辑 redissonLock.lock()的逻辑 r ...

  6. PCA-SIFT原理及源码解析

    相关: SIFT原理与源码解析 SURF原理与源码解析 ORB原理与源码解析 FAST原理与源码解析 BRIEF描述子原理与源码解析 Harris原理与源码解析 转载请注明出处:http://blog ...

  7. Spring Boot 核心原理与源码解析 - 目录

    准备重新写 SpringBoot 配置文件解析原理 , 先在这里把要写的内容记下来 Spring Boot 核心原理与源码解析 - 目录 1\何时解析\如何解析 application.propert ...

  8. 【特征匹配】BRIEF特征描述子原理及源码解析

    相关:Fast原理及源码解析 Harris原理及源码解析 SIFT原理及源码解析 SURF原理及源码解析 转载请注明出处: http://blog.csdn.net/luoshixian099/art ...

  9. 视频教程-YOLOv3目标检测:原理与源码解析-计算机视觉

    YOLOv3目标检测:原理与源码解析 大学教授,美国归国博士.博士生导师:人工智能公司专家顾问:长期从事人工智能.物联网.大数据研究:已发表学术论文100多篇,授权发明专利10多项 白勇 ¥78.00 ...

最新文章

  1. mybatis中![CDATA[]]的作用
  2. [linux]windows无法访问samba的安全性问题(关闭selinux)
  3. 每日一皮:今天在公司听到一句惨绝人寰骂人的话
  4. 如何在Go中编写防弹代码:不会失败的服务器工作流程
  5. php 内存队列,memcache构建简单的内存消息队列_PHP教程
  6. 三星p7510 android 8.0,7510更新到安卓8.0了。。。
  7. windows部署免安装版python
  8. 分析udp数据报_Linux内核网络udp数据包发送(二)——UDP协议层分析
  9. java string su_JAVA基础--常用类String
  10. 2021总结-》2022年
  11. java axis2 开发webservice_一、Axis2 WebService开发准备工作
  12. uml通信图画法_UML9种图的画法
  13. 知识分享!电阻按照用途分类可以分为哪几种电阻?-道合顺大数据infinigo
  14. ecmascript2015
  15. pytorch深度学习出错整理(一)
  16. Android性能:内存篇之内存回收
  17. Linux下TBB安装及编译
  18. 求二维数组的行列大小(C语言)
  19. 《CSS禅意花园》读后感
  20. 2021年中国固定资产投资现状分析:全国固定资产投资(不含农户)54.45万亿元[图]

热门文章

  1. 2014年4月java程序设计,2014年4月 Java语言程序设计(一)试题答案.doc
  2. JVM结构与OOM问题分析
  3. 正则表达式的一些符号基础和实例解析
  4. windows server 2012 磁盘整理功能 试用 chkdsk
  5. 卡尔曼滤波器实例:进一步讨论自由落体运动
  6. Linux 目录栈及目录切换
  7. Java 数据库连接池的技术选型都应考虑哪些要素
  8. 防止网页后退--禁止缓存
  9. SQL Server 2005数据库快照
  10. 通过javascript动态显示界面控件