HashMap

HashMap

1.了解数据结构中的HashMap么?能跟我聊聊他的结构和底层原理么?

HashMap由数组和链表组合构成的数据结构。数组里面每个地方都存了Key-Value这样的实例,在Java7叫Entry在Java8中叫Node。在put插入的时候会根据key的hash去计算一个index值。

2.你提到了还有链表,为啥需要链表,链表又是怎么样子的呢?

我们都知道数组长度是有限的,在有限的长度里面我们使用哈希,哈希本身就存在概率性,两个不同的key,hash有一定的概率会一样,那就形成了链表。每一个节点都会保存自身的hash、key、value、以及下个节点。

3.说到链表我想问一下,你知道新的Entry节点在插入链表的时候,是怎么插入的么?

java8之前是头插法,就是说新来的值会取代原有的值,原有的值就顺推到链表中去,因为写这个代码的作者认为后来的值被查找的可能性更大一点,提升查找的效率。但是,在java8之后,都用尾部插入了。

4. 为啥改为尾部插入呢?

4.1什么时候resize呢?

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

当前的容量大小为100,当你存进第76个的时候,判断发现需要进行resize了,那就进行扩容。

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

index = HashCode(Key)&(Length - 1)

现在我们要在容量为2的容器里面用不同线程插入A,B,C,假如我们在resize之前打个短点,那意味着数据都插入了但是还没resize那扩容前可能是这样的。我们可以看到链表的指向A->B->C

uploading.4e448015.gif正在上传…重新上传取消uploading.4e448015.gif正在上传…重新上传取消uploading.4e448015.gif正在上传…重新上传取消

因为resize的赋值方式,也就是使用了单链表的头插入方式,同一位置上新元素总会被放在链表的头部位置,在旧数组中同一条Entry链上的元素,通过重新计算索引位置后,有可能被放到了新数组的不同位置上。java8之后链表有红黑树

使用头插会改变链表的上的顺序,但是如果使用尾插,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了。

Java7在多线程操作HashMap时可能引起死循环,原因是扩容转移后前后链表顺序倒置,在转移过程中修改了原来链表中节点的引用关系。  Java8在同样的前提下并不会引起死循环,原因是扩容转移后前后链表顺序不变,保持之前节点的引用关系。

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

我认为即使不会出现死循环,但是通过源码看到put/get方法都没有加同步锁,多线程情况最容易出现的就是:无法保证上一秒put的值,下一秒get的时候还是原值,所以线程安全还是无法保证。

  1. 那我问你HashMap的默认初始化长度是多少?

初始化大小是16,创建HashMap的时候,阿里巴巴规范插件会提醒我们最好赋初值,而且最好是2的幂。之所以选择16,是为了服务将Key映射到index的算法。

  1. index的计算公式:index = HashCodeKey & Length- 1
  1. 那为啥用16不用别的呢?

因为在使用是2的幂的数字的时候,Length-1的值是所有二进制位全为1,这种情况下,index的结果等同于HashCode后几位的值。只要输入的HashCode本身分布均匀,Hash算法的结果就是均匀的。这是为了实现均匀分布。

8.重写equals方法的时候需要重写hashCode方法呢?你能用HashMap给我举个例子么?

因为在java中,所有的对象都是继承于Object类。Ojbect类中有两个方法equals、hashCode,这两个方法都是用来比较两个对象是否相等的。

在未重写equals方法我们是继承了object的equals方法,那里的 equals是比较两个对象的内存地址,显然我们new了2个对象内存地址肯定不一样

  1. 对于引用对象,比较的是两个对象的地址
  2. 对于值对象,==比较的是两个对象的值

大家是否还记得我说的HashMap是通过key的hashCode去寻找index的,那index一样就形成链表了,查找相同index的kv用equals!是的,所以如果我们对equals方法进行了重写,建议一定要对hashCode方法重写,以保证相同的对象返回相同的hash值,不同的对象返回不同的hash值。不然一个链表的对象hashCode都一样。

9.Hashmap线程不安全的,那你能跟我聊聊你们是怎么处理HashMap在线程安全的场景么?

我们一般都会使用HashTable或者ConcurrentHashMap,但是因为前者的并发度的原因基本上没啥使用场景了,所以存在线程不安全的场景我们都使用的是ConcurrentHashMap。

HashTable我看过他的源码,很简单粗暴,直接在方法上锁,并发度很低,最多同时允许一个线程访问,ConcurrentHashMap就好很多了,1.7和1.8有较大的不同,不过并发度都比前者好太多了。

HashMap常见面试题:

  1. HashMap的底层数据结构?
  2. HashMap的存取原理?
  3. Java7和Java8的区别?
  4. 为啥会线程不安全?
  5. 有什么线程安全的类代替么?
  6. 默认初始化大小是多少?为啥是这么多?为啥大小都是2的幂?
  7. HashMap的扩容方式?负载因子是多少?为什是这么多?
  8. HashMap的主要参数都有哪些?
  9. HashMap是怎么处理hash碰撞的?
  10. hash的计算规则?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值