你真的掌握HashMap原理吗?面试14连问

1.了解HashMap吗?能说一下它的结构和底层原理吗?

HashMap是常用的数据结构,由数组和链表组合构成的数据结构。
每个数组里都存了Key-Value这样的实例,在Java7中叫Entry在Java8中叫Node。
在这里插入图片描述

在进行put操作插入的时候,哈希函数会根据key值计算出哈希值,计算出index的值。在index位置插入该组数。

2.你提到了列表,为啥需要链表,链表是什么样子的?

数组的长度是有限的,在使用哈希值计算索引的时候,有一定的概率会发生hash冲突。即两个不同的key计算出相同的index。这时候就要使用链表~

每个节点都会保存自身的哈希值、key、value、以及下一个节点node

3.链表再插入的时候,是怎么插入的呢?

java8之前都是头插法,也就是说后来的值会取代原有的值,原来的值就推到了链表上去了。因为写这个代码的作者认为后来的值被查找的可能性会大一点,这样可以提升查找的效率。

但是在java8之后,都是用尾插法了。

4.为什么要改为尾部插入呢?

这个涉及到HashMap的扩容机制,数组的容量是有限的,数据多次插入的时候,到达一定的数量就会进行扩容,也就是resize。

5.什么时候会resize呢?

有两个因素:

  • Capacity: HashMap当前的长度
  • LoadFactor: 负载因子,默认值为0.75

数组容量 = Capacity*LoadFactor
举个栗子:数组容量为100,负载因子为0.75.那么数组中存入第76个时,就会触发扩容。

6.为什么要有负载因子,负载因子为什么是0.75?

为什么是0.75,为了兼顾时间和空间,加载因子过大的时候,会有更多的哈希冲突,数据不够散列,增加查询的开销(put和get都会用到查询)。加载因子过小,浪费空间。根据牛顿二项式定理得知,在数组长度为无穷的时候,0.63为最优值。

7.扩容?它是怎么扩容的?

分为两步:

  • 扩容:创建一个新的Entry空数组,长度为原数组的两倍
  • ReHash: 遍历原Entry数组,把所有的Entry重新Hash到新数组

8.为什么要重新Hash,直接复制过去不香么?

这个涉及到了索引值的计算规则。
index = HashCode(key) & (Length - 1)
原来的长度变了,索引值也就随之改变了。

4.言归正传,为什么要改为尾部插入呢?

单线程使用HashMap是安全的,但一旦涉及到多线程扩容的时候。可能会形成链环,内存被100%占用。尾部插入法在扩容的时候会保持链表元素原本的顺序,就不会出现链表成环的问题。

9.那是不是意味着Java8就可以在HashMap中使用多线程呢?

不是的,因为在Put/Get的源码中都没有加同步锁,因此多线程情况下,无法保证上一秒Put的值,下一秒Get到原值,线程安全无法保证。比如当两个线程同时put时,恰巧两个hash值一样的,并且该index数据为Null。源码中显示如果为Null,直接插入。A进入后还没插入被挂起,B正常执行插入数据。然后A获取CPU的时间片,但是A不会再进行hash判断,问题出现:线程A会把线程B插入的数据给覆盖掉,发生线程不安全。

小结:

1.在jdk1.7中,在多线程环境下,扩容时会造成环形链或数据丢失。
2.在jdk1.8中,在多线程环境下,会发生数据覆盖的情况。

10.jdk1.8在hashMap上还有什么优化吗?

HashMap在JDK1.8的版本中引入了红黑树结构做优化,当链表元素个数大于等于8时,链表转换成树结构;若桶中链表元素个数小于等于6时,树结构还原成链表。因为红黑树的平均查找长度是log(n),长度为8的时候,平均查找长度为3,如果继续使用链表,平均查找长度为8/2=4,这才有转换为树的必要。链表长度如果是小于等于6,6/2=3,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短。还有选择6和8,中间有个差值7可以有效防止链表和树频繁转换。假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。

11.HashMap的初始化大小是多少?为什么?

初始化大小是16,取得都是2的幂次方。原因是位运算比算数运算效率高的多

再看下index的计算公式:index = HashCode(Key) & (Length - 1)

2的n次方 - 1 的二进制全都是1。这是取模的的结果就等于是HashCode的最后几位值,只要HashCode本身分布均匀,Hash算法的结果就是均匀的。为了实现均匀分布

12.为啥重写equals方法要重写HashCode方法呢? 能用HashMap举个例子吗

[面试]Java基础知识

13.怎么处理HashMap的线程安全问题

有人曾因为HashMap扩容时会有概率发生链环的bug,sun公司不承认这个BUG, 因为他们官方是建议在单线程下使用HashMap,多线程下使用CurrentHashMap或者HahTable

HashTable直接在方法上上锁,并发度很低;初始size是11,扩容:newSize = oldSize * 2 + 1。CurrentHashMap并发度高,并且1.7和1.8也有很大的不同。

14.咱们聊聊CurrentHashMap

ConcurrentHashMap原理面试13连问

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值