HashMap底层原理

目录

1、底层数据结构:

2、常用变量

3、哈希算法

4、HashCode

5、HashMap的原理

6、为什么1.8中引入红黑树?

7、为什么HashMap链表会形成死循环?

8、HashMap线程安全吗?

9、JDK1.7,Put方法与Get方法

10、JDK1.8,Put方法与Get方法

11、HashMap的插入流程

12、HashMap与Hashtable的区别

13、HashSet和HashMap区别

14、HashMap和ConcurrentHashMap的区别?

15、HashMap与TreeMap的区别:


1、底层数据结构:

        1、JDK1.7及之前HashMap底层采用数组+链表的结构,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的。

        2、JDK1.8把它设计成达到一个特定的阈值(默认为8)之后,就将链表转成红黑树。

        3、不是用红黑树来管理hashmap,而是在hash值相同的情况下(而重复数量大于8),用红黑树来管理数据。红黑树相当于排序数据可以自动的使用二分法进行定位。

        4、当个数不多的时候,直接链表遍历更方便,实现起来也简单。而红黑树的实现要复杂得多。因为红黑树需要进行左旋,右旋操作,而单链表不需要,以下都是单链表与红黑树结构对比。

2、常用变量

        (1)HashMap的默认初始容量为16,容量必须是2的N次方。最大容量为2^30。(计算索引位置的公式:(n-1)&hash,当n为2的n次方时,n-1为低位全是1的值,此时任何值跟n-1进行&运算的结果为该值的低N位,达到了和取模同样的效果,实现了均匀分布。)

        (2)默认加载因子0.75。扩容阈值 = 容量 * 负载因子

        (3)当链表长度过长时,会有一个阈值,超过这个阈值8就会转化为红黑树,当红黑树上的元素减少到6个时就会退化为链表。

3、哈希算法

        把任意长度值(key)通过散列算法变换成固定的key(地址),通过这个地址进行访问的数据结构。

4、HashCode

        通过字符串(key)算出它的ASCII码,进行mod(取模),算出哈希表的下标。

5、HashMap的原理

        HashMap基于hashing原理,我们通过put()和get()方法存储和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,然后找到bucket位置来存储值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会存储在链表的下一个节点中。HashMap在每个链表节点中存储键值对对象。

        当两个不同的输入值,根据同一散列值的现象,我们就把它叫做碰撞(哈希碰撞、哈希冲突)。

HashMap扩容原理:

        (1)扩容:创建一个新的entry空数组,长度是原数组的2倍。

        (2)Rehash:遍历原entry数组,把所有entry重新hash到新数组内。

6、为什么1.8中引入红黑树?

        当我们的HashMap中存在大量数据时,加入我们mougebucket下对应的链表有n个元素,那么遍历时间复杂度就为0(n),为了针对这个问题,JDK1.8在HashMap中新增加了红黑树的数据结构,进一步使得遍历复杂度降低至0(logn);

总结一下HashMap是使用了哪些方法来有效解决哈希冲突:

        1、使用链地址法(使用散列表)来链接拥有相同hash值的数据

        2、使用二次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均

        3、引入红黑树进一步降低遍历的时间复杂度,使得遍历更快;

7、为什么HashMap链表会形成死循环?

        因为resize的赋值,单线程不会出现这种情况,多线程情况下resize会出现线程堵塞方式,线程1准备处理节点,线程2把HashMap扩容成功,此时链表已经逆向排序,那么线程1在处理节点时就可能会出现环形链表。

        因为JDK1.7是采用头插法,在多线程环境下有可能会使链表形成环状,从而导致死循环。JDK1.8做了改进,用的是尾插法,不会产生死循环。

                

8、HashMap线程安全吗?

        不安全,HashMap在并发下存在数据覆盖、遍历的同时进行修改会抛出ConcurrentModificationException异常等问题,JDK1.8之前还会出现死循环等问题。

9、JDK1.7,Put方法与Get方法

        Put: 1、判断当前数组是否需要初始化

                   2、如果key为空,则put一个空值进去

                   3、根据key计算出hashcode

                   4、根据计算出的Hashcode定位出所在桶

                   5、如果同是一个链表则需要遍历判断里面的hashcode、key是否和传入key相等,如果相等则进行覆盖,并返回原来的值。

                    6、如果同时空的,说明当前位置没有数据存入;新增一个Entry对象写入当前位置

        Get:1、首先根据key计算出hashcode,然后定位到具体的桶中

                  2、判断该位置是否为链表

                  3、不是链表就根据key、key的hashcode是否相等来返回值

                  4、为链表则需要遍历直到key及hashcode相等时返回值

                  5、啥都没取到就直接返回null

10、JDK1.8,Put方法与Get方法

        Put:1、判断当前桶是否为空,空的就需要初始化(resize 中会判断是否进行初始化)。

                  2、根据当前 key 的 hashcode 定位到具体的桶中并判断是否为空,为空表明没有 Hash 冲突就直接在当前位置创建一个新桶即可

                  3、如果当前桶有值( Hash 冲突),那么就要比较当前桶中的 key、key 的 hashcode 与写入的 key 是否相等,相等就赋值给 e,在第 8 步的时候会统一进行赋值及返回。

                  4、如果当前桶为红黑树,那就要按照红黑树的方式写入数据。

                  5、如果是个链表,就需要将当前的key、value封装成一个新节点写入到当前桶的后面

                  6、接着判断当前链表的大小是否大于预设的阈值,大于就要转换成红黑树

                  7、如果在遍历过程中找到key相同时直接退出遍历

                  8、如果 e != null 就相当于存在相同的key,那就需要将值覆盖

                  9、最后判断是否需要进行扩容

        Get:1、首先将key值hash之后取得所定位的桶。

                  2、如果桶为空则直接返回null

                  3、否则判断桶的第一个位置(有可能是链表、红黑树)的key是否为查询的key,时就直接返回value。

                   4、如果第一个不匹配,则判断它的下一个是红黑树还是链表

                   5、红黑树就按照树的查找方式返回值

                   6、不然就按照链表的方式遍历匹配返回值

11、HashMap的插入流程

        

 

12、HashMap与Hashtable的区别

        相同点:都是实现Map接口(hashTable还实现了Dictionary抽象类)

        不同点: 1、Hashtable继承Dictionary,HashMap继承的是Java1.2出现的Map接口

                        2、HashMap去掉了Hashtable的contains方法,但是加上了containsValue和containsKey方法。

                        3、HashMap允许空键值,而Hashtable不允许

                        4、HashTable是同步的,而HashMap是非同步的,效率上比HashTable要高。也就是说HashMap更适合于单线程环境,而HashTable适合于多线程环境。

                        5、HashMap的迭代器(Iterator)是fail-fast迭代器,HashTable的enumerator迭代器不是fail-fast的。

                        6、HashTable中数组默认大小是11,扩容方法是old * 2+1,HashMap默认大小是16,扩容每次为2的指数大小。

一般不建议使用HashTable。主要原因两点:

        1、HashTable是遗留类,内部实现很多没优化和冗余。

        2、即使在多线程环境下,现在也有同步的ConcurrentHashMap替代,没有必要因为是多线程而用Hashtable。

13、HashSet和HashMap区别

HashSet底层就是基于HashMap实现的。只不过HashSet里面的HashMap的value都是同一个Object而已,因此HashSet也是非线程安全的。

HashMapHashSet
实现了Map接口实现Set接口
存储键值对仅存储对象
调用put()向map中添加元素调用sdd()方法向set中添加元素
HashMap使用键(Key)计算HashcodeHashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false
HashMap相对于HashSet较快,因为它是使用唯一的键获取对象HashSet较HashMap来说比较慢

14、HashMap和ConcurrentHashMap的区别?

        ConcurrentHashMap是线程安全的HashMap的实现。主要区别如下:

        1、ConcurrentHashMap对整个桶数组进行分割分段,然后在每个分段上都用lock锁进行保护,相对于HashTable的syn关键字锁的粒度更精细了一些,并发性能更好。而HashMap没有锁机制,不是线程安全的。

        2、HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。

15、HashMap与TreeMap的区别:

        1、HashMap通过hashcode对其内容进行快速查找,而TreeMap中所有的元素都保持某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的)。

        2、在Map中插入、删除和定位元素,HashMap是最好的选择。但如果您按自然顺序或自定义顺序遍历键,那么TreeMap会更好。使用HashMap要求添加的键类明确定义了hashCode()和equals()的实现。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

互联网底层民工

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值