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


相关推荐

  • 经典中的经典算法:动态规划(详细解释,从入门到实践,逐步讲解)

    经典中的经典算法:动态规划(详细解释,从入门到实践,逐步讲解)首先,本博客为原创作品,欢迎指导,随意转载,如果可以请转载时说明出处,附上本文链接,谢谢动态规划的重要性就不多说,直接进入正题首先,我们看一下官方定义:定义:动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题…

    2022年4月27日
    53
  • python编程考试有哪些(python编程考试模拟题)

    python编程考试有哪些(python编程考试模拟题)2021国内外主流机器人编程赛事+等级考试Scratch编程、C++编程、Python编程等多个赛项,评比类、竞技类不同比赛形式自主选择。多个国内外主流机器人编程赛事,总能帮助孩子找到施展能力、表现创意的舞台。机器人、编程、人工智能等级考试篇全国青少年机器人技术等级考试和全国青少年软件编程等级考试均由中国电子…。2021机器人编程赛事+等级考试攻略之国内外主流赛事及能力测评篇上周,玛酷在公众号发布了一篇名为《2021机器人编程赛事+等级考试攻略之教育部白名单赛事篇》的文章。文章中为大家介绍了20

    2022年5月17日
    62
  • 转—如何让前端更安全?——XSS攻击和防御详解[通俗易懂]

    转—如何让前端更安全?——XSS攻击和防御详解

    2022年3月1日
    49
  • matlab中wavedec2,Matlab中wavedec使用學習及詳解 | 学步园

    matlab中wavedec2,Matlab中wavedec使用學習及詳解 | 学步园最近在使用對ECG訊號進行小波係數的提取,所以用到了wavedec這個matlab中内置函數。然後盡心學習,發現還挺高深,不是我學到的衹是一點皮毛,因爲要對一段波形進行特徵提取,所以我要看哪個小波係數最能代表波形特徵,然後進行保留。我使用的是:[C,L]=WAVEDEC(X,N,’wname’)這個會返回信號X在levelN(小波分解級數)的小波分解係數。輸出爲兩部分,第一部分C爲小波分解係…

    2022年7月23日
    10
  • MariaDB安装教程(保姆级)

    MariaDB安装教程(保姆级)Maria的详细下载过程

    2022年6月12日
    133
  • linux(6)查看进程ps命令「建议收藏」

    linux(6)查看进程ps命令「建议收藏」ps命令Linuxps(英文全拼:processstatus)命令用于显示当前进程的状态,类似于windows的任务管理器查看所有进程ps-A显示所有进程信息,连同命令行ps-

    2022年7月28日
    7

发表回复

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

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