HashMap常见面试总结

1.HashMap底层原理

(1)在jdk1.8中 底层是数组 链表 红黑树实现
在Hashmap中,初始化长度为16,当用put方法存储数据时,传入key及value值,
此时将key值进行hash运算后的hash值作为entry键值对在数组中的索引位置,确定位置之后,首先判断该位置是否为空,如果为空,就将entry值放在该位置,否则将entry以链表的方式存在数组中,当链表长度超过8位之后,会将链表转换为红黑树继续存储entry。
(2)在jdk1.8之前,底层是通过数组+链表实现的,当我们创建hashmap时会先创建一个数组。
当我们用put方法存数据时,先根据key的hashcode值计算出hash值,然后用这个哈希值确定在数组中的位置,再把value值放进去,如果这个位置本来没放东西,就会直接放进去;如果之前就有,就会生成一个链表,把新放入的值放在头部。
当用get方法取值时,会先根据key的hashcode值计算出hash值,确定位置,再根据equals方法从该位置上的链表中取出该value值。
当容量超过当前容量的0.75倍之后,就会自动扩容为原来容量的2倍。这个0.75就是负载因子。
但是在jdk1.8之后,最大的不同就是其由 数组+链表+ 红黑树组成。因为在1.7的时候,这个链表的长度不固定,所以如果key的hashcode重复之后,
那么对应的链表的数据的长度就无法控制了,get数据的时间复杂度就取决于链表的长度了,为了提高这一部分的性能,
加入了红黑树,如果链表的长度超过8位之后,会将链表转换为红黑树,极大的降低了时间复杂度

HashMap 线程不安全,有多个线程同时 HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以使用 ConcurrentHashMap。

2.hashmap和hashtable的区别?

相同点:
1、都是key-value 的双列集合;
2、都是数组+链表的底层原理;
3.都实现了Map 接口。
不同点:
1、继承的父类不同
Hashtable 继承Dictionary类,而HashMap 继承Abstract Map类。
2、线程安全性不同
hashMap 允许null键和null值为空,线程不同步,不安全,效率高,hashtable 不允许null键和null 值,线程同步,安全,效率低。
在java 开发中常用的是HashMap 类,比如ConcurrentHashMap,可以实现线程安全,Hashtable 和vector 一样成为了废弃类。

3.ConcurrentHashMap jdk1.8有什么优化?

取消segments字段,直接采用transient 、volatile、 HashEntry
<K,V>[] table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,避免并发冲突的问题。
在table数组+单向链表的数据结构,添加了红黑树的结构。当链表长度大于8时,链表
将转变为红黑树的结构,那么可以降低查询的时间复杂度,可以改进性能。

4.介绍下ConcurrentHashMap

ConcurrentHashMap是线程安全的HashMap,内部采用了的"分段锁"策略,它的主干是个Segment数组,通过继承ReentrantLock 来进行加锁。
所以每次锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。
一个Segment就是一个子哈希表,默认有16 个 Segment。所以理论上,只要它们的操作分别分布在不同的 Segment 上,最多可以同时支持 16 个线程并发写。

5.为什么要转为红黑树?为什么不直接开始就使用红黑树?

1)因为当长度过长,遍历链表的时间也会原来越长,用红黑树可以减少遍历时间
2)如果一开始就使用红黑树,那么就要进行左旋,右旋,变色等操作,在元素个数较小的时候会消耗时间,并且遍历时间消耗与链表没什么区别。

6.可不可以使用二叉树,不用红黑树?为什么阈值是8?

1)可以使用二叉树,但是使用二叉树可能会出现只有左子树或者右子树的情况,这样和链表没什么区别
2)阈值是8是因为泊松分布,单个hash槽中元素为8的概率小于百万分之一,所以选择7为分水岭,为7不做操作
7.一般使用什么作为key?
1) 一般使用String,Integer这种不可变类作为key,因为这样的话在对象创建之后hashcode就是定值, 并且这种类已经很好的实现了hashcode与equals方法的重写。

8.为什么重写equals方法之后还要重写hashcode方法?

1)因为hashcode生成是一串定长的数字,当数据量很大时候,难免会出现不同对象hashcode相同的情况。
也就是说hashcode相同,元素不一定相同,hashcoede不同,元素一定不同。

9.hashmap如何解决hash冲突,为什么hashmap中的链表需要转成红黑树?

1)hash冲突之后使用拉链解决,当链表的长度超过8会转为红黑树
2)因为当长度过长,遍历链表的时间也会原来越长,用红黑树可以减少遍历时间
Hash碰撞:一个hashmap集合中,当hash值重复,一般我们称为hash碰撞,HashMap中使用链表来解决碰撞问题,
当发生碰撞了,对象将会储存在链表的下一个节点中,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),
则使用红黑树来替换链表,从而提高速度。

10.HashMap的长度为什么要是2的n次方

HashMap为了存取高效,要尽量较少碰撞,就是要尽量把数据分配均匀,每个链表长度大致相同,这个实现就在把数据存到哪个链表中的算法;
这个算法实际就是取模,hash%length,计算机中直接求余效率不如位移运算,源码中做了优化hash&(length-1),
hash%length==hash&(length-1)的前提是length是2的n次方;
为什么这样能均匀分布减少碰撞呢?2的n次方实际就是1后面n个0,2的n次方-1 实际就是n个1;
例如长度为9时候,3&(9-1)=0 2&(9-1)=0 ,都在0上,碰撞了;
例如长度为8时候,3&(8-1)=3 2&(8-1)=2 ,不同位置上,不碰撞;

11.HashMap扩容机制

扩容(resize)就是重新计算容量,向HashMap对象里不停的添加元素,而HashMap对象内部的数组无法装载更多的元素时,
对象就需要扩大数组的长度,以便能装入更多的元素。当然Java里的数组是无法自动扩容的,
方法是使用一个新的数组代替已有的容量小的数组,就像我们用一个小桶装水,如果想装更多的水,就得换大水桶。
底层是resize方法中的transfer方法将原有的Entry数组的元素拷贝到新的Entry数组里,扩容都是以2的N次幂进行扩容 一般是2倍。

12.HashMap中put方法的过程?

调用哈希函数获取Key对应的hash值,再计算其数组下标;
如果没有出现哈希冲突,则直接放入数组;如果出现哈希冲突,则以链表的方式放在链表后面;
如果链表长度超过阀值等于8,就把链表转成红黑树,链表长度低于6,就把红黑树转回链表;
如果结点的key已经存在,则替换其value即可;
如果集合中的键值对大于12,调用resize方法进行数组扩容。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

明天会更好fjy

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

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

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

打赏作者

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

抵扣说明:

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

余额充值