【JAVA】HashMap底层实现原理浅谈

                                  HashMap底层实现原理浅谈

不论是实习还是正式工作,HashMap的底层实现原理一直是问地频率最高的一个内容,今天记录一下自己对HashMap的理解,如有不当之处,还请各位大佬指正。


一、前置名词解释

(1)哈希表

哈希表的主体躯干就是数组,我们可以利用下标对元素进行快速索引。新增一个元素时,元素的插入位置由哈希函数来定。

(2)哈希算法/哈希函数

哈希函数的作用是将一个不固定长度的二进制值经过运算,转化为一个固定长度的二进制值。将插入元素的key进行运算后,得到某个index值,从而确定在数组中的存储位置。但多个不相同的元素,经过哈希函数运算后,可能得到相同的index,这就出现了哈希冲突。

(3)哈希冲突/哈希碰撞

再好的哈希算法在有限长度的哈希表上,都会造成哈希冲突。那么怎么解决冲突呢?有多种解决方案

【1】开放地址法:放弃本次运算得到的index,继续寻找下一块未被占用的空间

【2】再散列法

【3】链地址法:采用数组(哈希表主体)+链表(为了解决哈希冲突而存在)的形式,哈希表中存储数组,数组中的元素空间存储链表的头结点。当发生冲突时,将相应的index位置上旧元素的指针next指向新元素即可。HashMap采用的就是这种方法


二、HashMap的特点

(1)允许存放null键和null值,而HashTable都不允许

(2)不是线程安全的,而HashTable是线程安全的,当然我们可以使用ConcurrentHashMap来代替HashTable,而且ConcurrentHashMap的性能更好,因为它只对数组的一部分进行上锁,即分段锁,而不是锁住整个数组。

(3)数组的默认大小为16,源码中没有直接写16,而是1<<4。

  /**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

(4)默认的负载因子是0.75,表示的是,如果数组中已经存储的元素个数大于数组长度的75%,将会引发扩容操作。

    /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

(5)扩容后,新数组的长度为原来数组的两倍。数组的默认大小与扩容倍数共同决定了数组长度永远是一个2的幂。


三、HashMap的实现原理

(1)HashMap的主干

HashMap的主干是一个Entry数组,里面存放Entry对象,每一个Entry对象包含(key,value)键值対和next指针以及对应的hash值。[jdk1.8之前用的是Entry,jdk1.8之后用的是Node,两者基本等价]

 static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
      .........
 }

(2)put(key,value)的过程

【1】由key计算哈希值

【2】由哈希值计算在数组中的索引

【3】如果索引处的Entry为null的话,则直接在此处插入元素。如果索引出的Entry不为null的话,通过循环不断遍历链表查找是否有相同哈希值的key,如果有,再比较两个key的是否相同,当哈希值与key都相同时,则认为是同一个Entry对象并覆盖原对象的value值。当哈希值相同而key不相同时,则认为不是同一个Entry对象,那么在链表尾部新增元素。[jdk1.8之前插入链表头部,jdk1.8之后插入链表尾部]

当索引位置相同的key超过8个时,即链表长度大于8时,jdk1.8会将此链表转化为红黑树。


(3)get(key)的过程

【1】由key计算哈希值

【2】由哈希值计算在数组中的索引

【3】循环遍历链表,如果有某一个Entry的key与哈希值与搜索的key、哈希值都相同的话,则取出此key对应的value。


(4)扩容的过程

当数组中已经存储的元素个数大于数组长度的负载因子倍数时,将会引发扩容操作。

【1】创建一个长度为原来数组长度两倍的新数组。

【2】重新对原数组中的Entry对象进行哈希运算,以确定他们各自在新数组中的新位置。

当然扩容的过程也会有很多问题,比如在多线程的情况下,多个线程同时对数组进行扩容时,有可能会造成条件竞争,就会发生意料之外的结果。因此在多线程的情况下,还是得使用线程安全的HashMap,比如ConcurrentHashMap,或者是经过Collections.synchronized(HashMap<K,V>)封装后的HashMap。


四,总结

HashMap可以用在缓存处理,动态规划中的备忘录等,需要注意的是,HashMap不是线程安全的。

碍于博主才疏学浅,谈到的HashMap原理非常的粗浅,博主定会在日后的学习与工作中,不断的完善博文,努力提高博客的水准。

全部评论

相关推荐

不愿透露姓名的神秘牛友
07-29 12:06
点赞 评论 收藏
分享
不愿透露姓名的神秘牛友
07-24 13:36
点赞 评论 收藏
分享
白火同学:先说结论,准大三不是特别好找实习,boss沟通300+没有实习是很正常的情况。一是暑期实习时间太短了,二是在这么多准大四都找不到实习,从实习时间和掌握技术层面,企业会优先看他们。 再说简历,其实985本+准大三到这水平的简历也很优秀了,要说的话,项目经历可以再优化一下,可以基本围绕采取STAR原则,分为项目概述、技术架构、技术亮点、实现结果,再发给AI润色一下。 最后说操作,准大三的话,如果想找实习那就多投,不过现在也7月中旬了,时间上已经略晚了。如果7月底实在找不到,也可以多刷点算法,多学点技术,这实习也不至于一定得有,当然有更好。
点赞 评论 收藏
分享
不愿透露姓名的神秘牛友
06-21 11:33
昨天是学校最后一场招聘会,鼠鼠去参加了,全场只有一个招聘java的岗位,上来先做一份笔试题,做完后他拿张纸对答案,然后开始问简历上的问题,深圳小厂,6-8k(题目如下),后面还有两轮面试。然后我就在招聘现场逛呀逛,看到有公司招聘电商运营,给的比上年的小厂还多,鼠鼠就去了解了下,然后hr跟鼠鼠要了份简历,虽然我的简历上面全是求职Java开发相关的内容,但是hr还是鼓励我说没关系,她帮我把简历给老板看看,下周一会给我通知。招聘会结束后鼠鼠想了一段时间,也和朋友聊了聊,发现我可能是不太适合这个方向,然后就跟爸爸说回家了给我发条微信,我有些话想跟他说说。晚上爸爸到家了,跟我发了条微信,我立马跑出图书馆跟他打起了电话,这个通话长达一个小时,主要是跟爸爸坦白说我不想找这行了,是你的儿子太没用了,想试试其他行业。然后爸爸也跟我说了很多,说他从来没有希望我毕业后就赚大钱的想法,找不到就回家去,回家了再慢慢找,实在找不到就跟他干(帮别人装修房子,个体户),他也知道工作不好找,让我不要那么焦虑,然后就是聊一些家常琐事。对于后面的求职者呢我有点建议想提一下,就是如果招实习的时间或者秋招开始,而你的简历又很差的情况下,不要说等做好项目填充完简历之后再投,那样就太晚了,建议先把熟悉的项目写上简历,然后边投边面边完善,求职是一个人进步的过程,本来就比别人慢,等到一切都准备好后再投岂不是黄花菜都凉了。时间够的话还是建议敲一遍代码,因为那样能让你加深一下对项目的理解,上面那些说法只是针对时间不够的情况。当然,这些建议可能没啥用,因为我只是一个loser,这些全是建立在我理想的情况下,有没有用还需其他人现身说法。上篇帖子没想到学校被人认了出来,为了不丢脸只能匿名处理了。
KPLACE:找研发类或技术类,主要还是要1.多投 2.多做准备,很多方面都要做准备 3.要有心理准备,投累了就休息一两天,再继续,要相信自己能找到
投递58到家等公司10个岗位
点赞 评论 收藏
分享
07-25 13:42
门头沟学院 Java
安锋:看看老板的腿
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务