HashMap的数据结构浅析[通俗易懂]

HashMap的数据结构浅析[通俗易懂]HashMap是非线程安全的。而HashMap的线程不安全主要体现在resize时的死循环HashMap工作原理HashMap数据结构常用的底层数据结构主要有数组和链表。数组存储区间连续,占用内存较多,寻址容易,插入和删除困难。链表存储区间离散,占用内存较少,寻址困难,插入和删除容易。HashMap要实现的是哈希表的效果,尽量实现O(1)级别的增删改查。它的具体实现则是同时使用…

大家好,又见面了,我是你们的朋友全栈君。

HashMap是非线程安全的。而HashMap的线程不安全主要体现在resize时的死循环

HashMap工作原理

  • HashMap数据结构
    常用的底层数据结构主要有数组和链表。数组存储区间连续,占用内存较多,寻址容易,插入和删除困难。链表存储区间离散,占用内存较少,寻址困难,插入和删除容易。
  • HashMap要实现的是哈希表的效果,尽量实现O(1)级别的增删改查。它的具体实现则是同时使用了数组和链表,可以认为最外层是一个数组,数组的每个元素是一个链表的表头。
    HashMap数据结构

HashMap的寻址方式

  • 对于新插入的数据或者待读取的数据,HashMap将Key的哈希值对数组长度取模,结果作为该Entry在数组中的index。因此数组的长度必须时2的N次方,实际中,HashMap会自动通过Integer.highestOneBit算出比指定整数大的最小的N值。
        public static int highestOneBit(int i) {
          i |= (i >>  1);
          i |= (i >>  2);
          i |= (i >>  4);
          i |= (i >>  8);
          i |= (i >> 16);
          return i - (i >>> 1);
        }
  • 碰撞:由于数组的长度是有限的,所以不管Hash()方法和Equals()方法写得如何严谨,始终不能完全避免碰撞的发生(Hash值算出来的Index相同,需要放在数组的同一个位置),碰撞发生后,我们就只能添加一个链表子节点,但是这无疑会降低查找效率(找到Index还要遍历链表。。。)
  • 加载因子:默认是0.75,当数组的被占空间>=0.75的时候,HashMap就会进行扩容(变为两倍),扩容之后数组中的所有元素进行重排序(算出来的Index可能不同),从而减少数组下链表的长度,提高查找效率。

Java8 中的升级

  • Java8 中的HashMap采用了数组+链表+红黑树(类似于数据结构中的平衡二叉树)的模式(当链表长度<8时仍然采用链表的形式,>8时由链表的数据结构转换成红黑树的数据结构)
    Java8HashMap.png

JDK1.7中线程不安全的HashMap

  • 上文讲到占用空间超过加载因子的值时,就会自动扩容,这时HashMap中的元素或重新计算排序,这显然是不能保证线程安全的,而且在多线程并发调用时,可能出现死循环。
  • 首先:先给出resize的核心代码:
void transfer(Entry[] newTable, boolean rehash) {  
        int newCapacity = newTable.length;  
        for (Entry<K,V> e : table) {  
  
            while(null != e) {  
                Entry<K,V> next = e.next;           
                if (rehash) {  
                    e.hash = null == e.key ? 0 : hash(e.key);  
                }  
                int i = indexFor(e.hash, newCapacity);   
                e.next = newTable[i];  
                newTable[i] = e;  
                e = next;  
            } 
        }  
    }  
  • 在多线程调用中,可能会产生这种情况:两个线程同时认为HashMap需要rehash,这里就有一种可能性,这两个线程在调用的时候线程访问.png
  • 当两个线程痛时执行的时候,可能有一个会被挂起,等待另一个结束后在继续执行,这时问题就产生了;
    HashMap线程不安全图解.png
  • 产生死循环的原因是:while(null != e)这句判断,在第一次执行的时候,B原本->null,于是重新计算到B的时候B->null,判断体就结束执行,于是就扩容成功
  • 当第二个线程执行扩容的时候,内存中是B->A ,是的,条件成立,而原本的条件(线程2以为的条件)是A->B 这里,两个条件同时成立,后果就是。。。一直循环下去A->B->A->B…
    • 具体过程是A->B,执行while,将A头插到数组,指针指向下一个,B->A,条件成立,将B头插如数组,指针指向下一个,A->B…

JDK1.8的优化

  • 在JDK1.8中采用了尾插法,可以有效避免上述这种死循环的情况。

以上就是我目前的理解,还有一种使用迭代器时的fast-fail,以后有机会更新。
关于ConcurrentHashMap可以看看我的另外一篇,欢迎指正:ConcurrentHashMap浅析

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/144696.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Scrum 学习笔记

    Scrum 学习笔记

    2021年12月17日
    42
  • pycharm中使用anaconda部署python环境_anaconda创建python虚拟环境

    pycharm中使用anaconda部署python环境_anaconda创建python虚拟环境环境配置环境的配置分为三步:配置虚拟环境和安装程序所需要的包以及在pycharm中打开项目配置虚拟环境配置虚拟环境需要通过anaconda来完成,anaconda的下载地址为:https://docs.conda.io/en/latest/miniconda.htmlwindows用户下载python3.8的miniconda即可下载完毕之后双击安装即可,注意一点这些一定要选中程序安装完毕之后打开windows的命令行(cmd),输入condaenvlist,出现下列信息则表示con

    2022年8月25日
    6
  • url传递参数_url encode

    url传递参数_url encodeWerkzeug之URL路由原文链接http://werkzeug.pocoo.org/docs/0.12/routing/当需要组合控制器和视图函数时,我们需要一个调度器来实现。一个简单的实现方式是采用正则表达式匹测试路由信息,调用回调函数并返回结果。Werkzeug提供了一个类似Route[1]的强大功能.下文提到的所有对象都是从werkzeug.routing导入而不是

    2022年10月6日
    2
  • 最简单的基于FFMPEG的视频编码器(YUV编码为H.264)

    最简单的基于FFMPEG的视频编码器(YUV编码为H.264)本文介绍一个最简单的基于FFMPEG的视频编码器。该编码器实现了YUV420P的像素数据编码为H.264的压缩编码数据。编码器代码十分简单,但是每一行代码都很重要,适合好好研究一下。弄清楚了本代码也就基本弄清楚了FFMPEG的编码流程。目前我虽然已经调通了程序,但是还是有些地方没有完全搞明白,需要下一步继续探究然后补充内容。本程序使用最新版的类库(编译时间为2014.5.6),开发平台为VC2

    2022年7月16日
    18
  • scala , spark-shell 命令行无法撤销,删除 ,退格 ,回退

    scala , spark-shell 命令行无法撤销,删除 ,退格 ,回退scala , spark-shell 命令行无法撤销,删除 ,退格 ,回退

    2022年4月23日
    146
  • 什么是字符串常量池_常量池中的字符串是对象吗

    什么是字符串常量池_常量池中的字符串是对象吗关于字符串与字符串常量池JDK1.8-1.9,String底层从char数组变成了byte数组,原因是部分字符仅占一个byte,而堆中含有大量的String字符串,该优化能节省较多空间。StringTable为什么要调整(移入堆内)(JDK1.6-1.7)permSize默认比较小永久代垃圾回收频率低字符串拼接操作常量与常量的拼接结果在常量池,原理是编译器优化常量池中不会存在相同内容的常量只要其中一个是变量,结果就在堆中。变量拼接的原理是StringBuilder(final不算变量)

    2022年7月28日
    23

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号