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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • java中static的用法[通俗易懂]

    java中static的用法[通俗易懂]java中static的用法

    2022年7月9日
  • frp内网穿透设置_frp内网穿透原理

    frp内网穿透设置_frp内网穿透原理十分钟教你配置frp实现内网穿透一、frp的作用利用处于内网或防火墙后的机器,对外网环境提供http或https服务。 对于http,https服务支持基于域名的虚拟主机,支持自定义域名绑定,使多个域名可以共用一个80端口。 利用处于内网或防火墙后的机器,对外网环境提供tcp和udp服务,例如在家里通过ssh访问处于公司内网环境内的主机。二、配置说明1、…

    2022年9月14日
  • 前端缓存处理[通俗易懂]

    前端缓存处理[通俗易懂]前端的缓存原来是这样做的!

    2022年10月24日
  • getElementById怎么调用

    getElementById怎么调用getElementById怎么调用浏览次数:1414次悬赏分:0|解决时间:2011-4-2818:27|提问者:匿名最佳答案首先都有一个Document对象然后你就可以借这个方法来引用网页文件中各个标签的属性了,当然这个被你引用的标签必须具有ID属性;比如,上面那个标签img就具有ID属性,那么我想获取他的图片地址,可以这么用Document.get

    2022年7月15日
  • 图片批量重命名编号不带括号的_批量重命名不带括号

    图片批量重命名编号不带括号的_批量重命名不带括号如果你是一名摄影工作者,那么你的电脑里肯定会保存很多的图片,为了更好的整理和保存这些图片,比如一类主题的图片以相同文字命名并且编号,这样就方便以后查找了,解决办法有了,那么怎么给这么多图片命名并编号呢?必须是重命名的放大,这样可以一键修改文件名,但是这样修改后的图片名称虽然有编号,但是编号外面加了一层括号,比较难看,很多小伙伴不想要这个括号,所以今天小编就为大家详细介绍“图片批量重命名编号不要括号”的方法!需要用的工具软件:优速文件批量重命名工具软件下载地址:https://ww…

    2022年9月4日
  • Python3.X出现AttributeError: module ‘urllib’ has no attribute ‘urlopen’错误[通俗易懂]

    研究用Python写爬虫,下载一个网页。报错代码如下importurllibdefgetHtml(url):page=urllib.urlopen(url)html=page.read()returnhtmlhtml=getHtml(“http://www.baidu.com”)print(html)运行时报错:Attribute

    2022年4月12日

发表回复

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

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