小白也能看懂的JDK1.8前_HashMap的扩容机制原理

  最近在研究hashmap的扩容机制,作为一个小白,相信我的理解,对于一些同样是刚刚接触hashmap的白白是有很很大的帮助,毕竟你去看一些已经对数据结构了解透彻的大神谈hashmap的原理等,人家说的很高大上,时不时会夹着稍许的英文你也看不懂是吧,不过这样显得比较有逼格哈哈。在正文之前,我非常有必要给刚刚接触hashmap以及没有学过数据结构(其实数据结构我了解也不多哈哈)的小伙伴普及几个知识,你记住就行了:

     1. 对于刚接触hashmap,hashmap你就暂时理解为哈希表(hash表),结构为“数组+链表”;如果说到表的长度,那指的是数组长度。

     2. “数组+链表”的结构专业术语叫“链表散列”,说简单点,一个数组上的每个位置存储的一个单链表;实际上数组的每个位置记录着是单链表的第一个节点,有了第一个节点后面不就串起来了嘛

     3. hashmap在jdk1.8前的结构是“数组+(单)链表”,在jdk1.8,结构就引入了“数组+链表+红黑树”,不过在这里我们只讨论1.8之前

     4. hashmap在添加元素时使用的是“尾插法”,而在扩容时转移数据使用的是“头插法”;后半句话给我重点记着,后面的源码就涉及到这个

     5.hashmap扩容的本质是重新创建一个原有数组长度2倍的新数组。

     6. 新表的节点分布 并不一定 跟 旧表 一致。比如说旧表的oldTable[0]上有key(0)->key(1)->key(2),而到新表这里newTable[0]就可能变成了key(2)->key(0)【头插法】,而key(1)在其他位置了。

 

  好了,开始进入正题!我们来研究jdk1.8前的hashmap的扩容原理(为什么没有1.8呢?因为我还没去看哈哈);它扩容最核心的方法就是resize(),源码如下:

//hashmap的扩容方法
    void resize(int newCapacity) {
        Entry[] oldTable = table;    //把当前的hash表赋值给一个临时数组,这个临时数组代表 旧hash表
        int oldCapacity = oldTable.length; //得到旧hash表的长度
        if (oldCapacity == MAXIMUM_CAPACITY) { //如果判断旧hash表的长度(其实是数组长度)等于最大容量值
            threshold = Integer.MAX_VALUE; //把Integer.MAX_VALUE赋值给当前的阈值,它的意思就是不再扩容了
            return;
        }
 
        Entry[] newTable = new Entry[newCapacity];  //创建一个新的hash表
        transfer(newTable, initHashSeedAsNeeded(newCapacity)); //这个是重点,这个方法实现将旧hash表的数据放到新的hash表中
        table = newTable;  //当前的hash表换成新的hash表了
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);  // 有新表后,阈值是需要重新计算的
    }

  上面代码中,我们真正要关注的是transfer(),你看它方法名就取得很明显,转移的意思是吧,源码如下:

   /**
     *  把 旧表 的 数据 往新表上挪
     */
    void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length; //得到新表的长度
        for (Entry<K,V> e : table) {    //这里table是旧表,这里是遍历旧表,把旧表的数据往新表上转移
            while(null != e) {  //如果当前节点不为空
                Entry<K,V> next = e.next;      //next是一个临时变量,用来引用或保存当前节点指向的下一节点,比如说当前节点是key(3),key(3)->key(5),
                            那next就暂时保存key(5)
                if (rehash) { //重新计算hash
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity); //这个挺重要的,它是重新计算我们当前的(旧)节点应该放到新表的哪个位置上
                e.next = newTable[i];     //超级重点,它的作用是断开当前节点与旧表的联系,啥意思呢? 还是刚才的例子当前节点是key(3),key(3)->key(5),
                          key(3)现在不指向key(5)了,已经投奔(指向)新表上的某个位置了


                newTable[i] = e;           //超级重点+,头插法来了,把当前的节点赋值给新表的某个位置,作用就是旧节点复制到新表上了;意思是每轮循环的当前节点都会
                         插入到新数组上的某个单链表的第一个位置,作为单链表的头节点

                e = next;             //把刚才保存的下一节点 作为下一轮循环的当前节点
            }
        }
    }

  我尽量把解释都放到了源码上面,方便大家查看,减少不必要的页面上下滚动带来的眼睛疲劳(我真是活雷锋哈哈),后面我会去看jdk1.8的源码,会再次为大家分享。

  如果觉得说的勉强还行的话,点个推荐呗!

posted @ 2020-01-18 20:09  爱编程DE文兄  阅读(3473)  评论(0编辑  收藏  举报