HashMap是平时开发中使用最多的一个集合类之一,可以算作一个源码必读类,通过读源码可以了解其中内部原理,帮助我们去更好的使用。另外在实现上可以看得出大神们追求性能,子类与父类之间关系的精巧设计,有很多地方可以在日常开发中借鉴。、

HashMap使用了哈希表的数据结构来实现,简单来说一个Node数组。下面主要整理常用方法put,get,remove,以及补充与java7那些地方做了改进。

put方法:

    public V put(K key, V value) {
        //调用内部的一个方法,注意该方法是一个final类,子类不可覆盖,
        //具体的实现逻辑都在这个方法里了,至于为什么要单独抽成一个方法,可以看下put和putIfAbsent这两个方法,
        //他们调用的都是putVal方法,但是传入的onlyIfAbsent参数不一样,这个参数是用来判断是否需要覆盖原值
    }
    //
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
        //声明一个哈希表
        HashMap.Node[] tab;
        int n;
        //如果当前哈希表为空时,进行初始化哈希表,如果创建HashMap对象时没有指定大小,那么默认大小为16
        if ((tab = this.table) == null || (n = tab.length) == 0) {
            n = (tab = this.resize()).length;
        }
        //接下来会先开始根据hash去取找对应的所在哈希表的位置,公式:i = hash & (n-1)
        Object p;
        int i;
        //如果在哈希表中对应的数组角标下没有节点(Node),则直接创建新节点放在该角标下,返回null,modCount自增1,结束
        if ((p = tab[i = n - 1 & hash]) == null) {
            tab[i] = this.newNode(hash, key, value, (HashMap.Node)null);
        } else {
            Object e;
            Object k;
             //如果当前角标下已存在链表,则先判断头节点hash和待插入的hash是否一致,
             //再判断key是否相等或者key对象覆写的equals方法是否返回true
            if (((HashMap.Node)p).hash == hash && ((k = ((HashMap.Node)p).key) == key || key != null && key.equals(k))) {
                e = p;
            } else if (p instanceof HashMap.TreeNode) {
                //如果上面的与头节点对比结果是不一致,判断该头节点是否为红黑树节点,如果是则调用putTreeVal方法进入红黑树的插入逻辑
                e = ((HashMap.TreeNode)p).putTreeVal(this, tab, hash, key, value);
            } else {
                //如果该头节点是链表,那么则开始遍历链表,
                
                int binCount = 0;
                while(true) {
                    //如果链表中没有相同的key和key.equals()都返回false,那就在链表尾部进行插入新节点
                    //当插入节点后,当前链表长度大于等于8,并且此时哈希表长度大于等于64时,才会进行链表转化为红黑树,、
                    if ((e = ((HashMap.Node)p).next) == null) {
                        ((HashMap.Node)p).next = this.newNode(hash, key, value, (HashMap.Node)null);
                        if (binCount >= 7) {
                            //若仅仅链表长度大于等于8,但是哈希表长度小于64时,
                            //链表不会发生红黑树转换,而是会进行扩容,进行一次再散列
                            this.treeifyBin(tab, hash);
                        }
                        break;
                    }
                    //如果该链表下存在相同的key,则跳出循环
                    if (((HashMap.Node)e).hash == hash && ((k = ((HashMap.Node)e).key) == key || key != null && key.equals(k))) {
                        break;
                    }

                    p = e;
                    ++binCount;
                }
            }
            //这里是一个是否覆盖原值的操作,如果put的key在原HashMap已经存在了,那么onlyIfAbsent为false时则会覆盖原来的value
            //另外如果原来的value为null时,也会进行值的覆盖
            if (e != null) {
                V oldValue = ((HashMap.Node)e).value;
                if (!onlyIfAbsent || oldValue == null) {
                    ((HashMap.Node)e).value = value;
                }
                //这是一个访问节点之后的钩子方法,可以交给子类去覆盖,linkedHashMap的实现是将修改的节点放到维护的顺序链表的队尾
                this.afterNodeAccess((HashMap.Node)e);
                return oldValue;
            }
        }
        //修改次数自增1
        ++this.modCount;
        //容器size自增1,如果当前size大于扩容阈值。则进行扩容
        if (++this.size > this.threshold) {
            this.resize();
        }
        //这是一个添加节点之后的钩子方法,HashMap中没有具体逻辑,LinkedHashMap覆盖了这个方法,
        //是一个删除最久远节点的方法,默认没有使用
        this.afterNodeInsertion(evict);
        return null;
    }

下面是流程图

resize方法:

final Node<K,V>[] resize() {//声明一个变量指针指向HashMap中的node[],哈希表Node<K,V>[] oldTab = table;//原哈希表长度int oldCap = (oldTab == null) ? 0 : oldTab.length;//原扩容阈值,如果当前size大于扩容阈值时则会发生扩容int oldThr = threshold;//声明新哈希表长度以及新的扩容阈值int newCap, newThr = 0;//下面的逻辑比较绕,描述的时每次扩容大小以及再散列if (oldCap > 0) {//这里判断当前哈希表长度已经到达最大值(2^31-1)不在进行扩容,并且将扩容阈值设置为最大值if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}//将新哈希表长度变为原来的两倍,如果原哈希表长度大于等于16,那么扩容阈值也变为原来的两倍else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // double threshold}//这里是用来初始化哈希表长度,可以仔细看一下HashMap的构造方法,创建HashMap对象不会立即去初始化哈希表,// 而是先初始化负载因子和扩容阈值(默认16),初始化容器在resize中完成,这里可能也是设计者为了考虑空间利用吧,// 等到需要用的时候再去初始化容器else if (oldThr > 0) // initial capacity was placed in thresholdnewCap = oldThr;else {               // zero initial threshold signifies using defaults//如果使用的是new HashMap(0)构造方法创建HashMap时,则这里使用默认的配置,初始容量为16,扩容阈值为12//负载因子不得传0,否则会报错,可以看下三个构造函数,挺重要的newCap = DEFAULT_INITIAL_CAPACITY;newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}//这里是用来处理当 (1)原哈希表长度大于0且小于16 或者 (2)容器进行两倍扩容时新哈希表长度大于等于2^31-1 这两种情况时//对应上面的两种情况是 : 新的扩容阈值是newThr为 (1)新容器长度*负载因子(newCap * loadFactor)  (2)2^31-1//注意不是 HashMap非得当前size一旦达到当前哈希表长度*负载因子(oldCap * loadFactor)就会发生扩容,如果初始化HashMap哈希表长度小于16时,//会根据 length*loadfactor 进行扩容,如果初始化的哈希表长度为16或者16以上时,是根据当前size > length进行扩容,// 相当于一倍的哈希表长度if (newThr == 0) {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;//下面就是再散列的逻辑了//主要需要关注是遍历每一个链表如何将每一个节点重新找到在新哈希表的位置//再散列时使用的计算公式时 hash & oldCap ,而之前put操作时使用的是 hash & (oldCap - 1)//这里精妙之处是因为HashMap的长度用于是2的倍数,此时二进制数是1后面带着n个0,那么再进行-1操作时,二进制树的位数会减一,但是所有位数就全部都是1,//将原容器进行两倍扩容等到的值再进行-1时,其二进制数还是一堆1,但是和原容器长度-1的二进制数相比,新的容器长度的会比原来在高位上多一个1//所以此时只需要拿着hash原容器长度做一次与运算得到的结果时0还是1时,如果是0说明与最高位的那个1与结果是0,// 如果等于1说明这个hash与新的容器做hash & (newCap - 1)时,结果会hash & (oldCap - 1)多oldCap,可以自己推理下,// 在这里就可以算出拿到该节点在新哈希表下的角标时 原角标 + oldCap//那么为什么要这么做?//这样我认为能提高性能,节点hash与运算类似于10000000这种二进制数会比11111111速度更快些,因为这样算出来的结果只有0和1两种值if (oldTab != null) {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//具体再散列逻辑,先声明两个链表,一个是高位链表,一个是低位链表//高位链表: hiHead,hiTail,节点 hash & oldCap == 1,新的节点位置 原index+oldCap//低位链表:loHead,loTail,节点 hash & oldCap == 0 新的节点位置 还是原位置Node<K,V> loHead = null, loTail = null;Node<K,V> hiHead = null, hiTail = null;Node<K,V> next;do {next = e.next;if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}else {if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);if (loTail != null) {loTail.next = null;newTab[j] = loHead;}if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead;}}}}}return newTab;
}

下面是流程图

get方法:

remove方法:

HashMap java8内部原理总结相关推荐

  1. HashMap、ConcurrentHashMap原理分析

    集合(Collection)是编程中常用的数据结构,而并发也是服务器端编程常用的技术之一,并发总是离不开集合这类高级数据结构的支持.比如两个线程需要同时访问一个中间临界区(Queue),比如常会用缓存 ...

  2. HashMap底层实现原理,红黑树,B+树,B树的结构原理,volatile关键字,CAS(比较与交换)实现原理

    HashMap底层实现原理,红黑树,B+树,B树的结构原理,volatile关键字,CAS(比较与交换)实现原理 首先HashMap是Map的一个实现类,而Map存储形式是键值对(key,value) ...

  3. hashmap remove 没释放内存_java从零开始手写 redis(13)HashMap 源码原理详解

    为什么学习 HashMap 源码? 作为一名 java 开发,基本上最常用的数据结构就是 HashMap 和 List,jdk 的 HashMap 设计还是非常值得深入学习的. 无论是在面试还是工作中 ...

  4. Java HashMap的工作原理 及各种Map区别

    2019独角兽企业重金招聘Python工程师标准>>> 一.Java HashMap的工作原理 jdk1.7下HashMap数据结构:数组加链表,链表长度没有8的限制: jdk1.8 ...

  5. Java 集合深入理解 (十一) :HashMap之实现原理及hash碰撞

    文章目录 前言 哈希表原理 实现示例 HashMap实现原理 全篇注释分析 实现注意事项 默认属性分析 属性分析 构造方法分析 重要的put方法 总结 前言 哈希表(hashMap)又叫散列表 是一种 ...

  6. java8 stream原理

    1.Stream 流的介绍 1.1 java8 stream介绍 java8新增了stream流的特性,能够让用户以函数式的方式.更为简单的操纵集合等数据结构,并实现了用户无感知的并行计算. 1.2  ...

  7. HashMap面试底层原理(原作者很厉害)

    一个HashMap跟面试官扯了半个小时 前言 HashMap应该算是Java后端工程师面试的必问题,因为其中的知识点太多,很适合用来考察面试者的Java基础. 开场 面试官: 你先自我介绍一下吧! 安 ...

  8. java 1.8 hashMap的实现原理

    内部实现 搞清楚HashMap,首先需要知道HashMap是什么,即它的存储结构-字段:其次弄明白它能干什么,即它的功能实现-方法.下面我们针对这两个方面详细展开讲解. 存储结构-字段 从结构实现来讲 ...

  9. hashMap和hashTable的区别以及HashMap的底层原理?

    hashMap和hashTable的区别? 1.继承的父类不同 HashTable继承Dictionary类,而hashMap继承了AbstractMap类,但是二者都实现了map接口. 2.线程安全 ...

最新文章

  1. 课程第五天内容《基础交换 五》
  2. c++STL(标准模板库)理论基础
  3. 【测试点分析】1060 爱丁顿数 (25分)_21行代码
  4. 1bit和1byte_1byte等于( )bit_学小易找答案
  5. 洛谷 P1404 平均数
  6. shelve模块简单用法
  7. 7-1 近似求PI (15 分)
  8. 【优化算法】多目标蝙蝠优化算法(MOBA)【含Matlab源码 005期】
  9. ANSI SQL 定义
  10. 3S基础知识:在VC++中嵌入MapX的集成二次开发
  11. 入职体检的体检项目有哪些呢?
  12. javascript中this用法
  13. matplotlib画箱线图,添加非参数检验-秩和检验的结果
  14. OpenStack修改Guest用户密码——利用Qemu guest agent实现
  15. SIM800C实验记录之熟悉AT命令
  16. Oracle GoldenGate for MySQL部署踩的坑
  17. 网络基础2---->网络数据传输(局域网)
  18. Kodu程序的菜单---Kodu少儿编程第七天
  19. 少年碎碎念:《WHOLENESS》
  20. You probably need to get an updated matplotlibrc file from解决方法

热门文章

  1. 大数据入门第一块——dota2数据分析(一)
  2. 用python爬虫爬取微博信息
  3. 图可视化引擎G6与X6
  4. 地址、指针、指针变量详解
  5. 【Unity】Unity 3D中的内存管理
  6. endorsed java_Java利用endorsed如何覆盖jdk提供的类详解
  7. Linux系统联网与修改静态ip地址
  8. LDA主题模型中coherence(一致性)报错得出来为nan解决办法
  9. B. A Perfectly Balanced String? 思维 vector小技巧
  10. File,字符集基础知识,IO流学习(一)