zoukankan      html  css  js  c++  java
  • 小白也能看懂的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的源码,会再次为大家分享。

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

  • 相关阅读:
    被隐藏的文件更改为可见
    Selenium WebDriver多层表单切换
    for循环
    Java课程设计二次大作业
    Java-DAO模式代码阅读及应用
    编辑器、编译器、文件、IDE等常见概念辨析
    树、二叉树和查找等知识点的总结
    二叉树的实现
    二叉树顺序结构和链式结构的相互转换
    使用k-近邻算法改进约会网站的配对效果
  • 原文地址:https://www.cnblogs.com/ibcdwx/p/13780513.html
Copyright © 2011-2022 走看看