hashmap hash冲突怎么解决_10个HashMap问题搞定面试官

废话不多说,看题:

1、说一下HashMap的数据结构?

JDK1.7使用的是数组+ 单链表的数据结构。

JDK1.8及之后时,使用的是数组+链表+红黑树的数据结构。当阈值是默认阈值0.75,链表的深度大于等于8,数组容量大于等于64时,扩容的时候会把链表转成红黑树,时间复杂度从O(n)变成O(logN);当红黑树的节点深度小于等于6时,红黑树会转为链表结构。

2、简述下HashMap的工作原理?

JDK1.7使用的是数组+ 单链表的数据结构。

JDK1.8及之后时,使用的是数组+链表+红黑树的数据结构。

HashMap 通过 put & get 方法存储和获取。

put() 方法存储对象:

、调用 hash(key.hashCode()) 方法计算 key 的 hash 值(其中JDK1.7用了9次扰动处理=4次位运算+5次异或;JDK1.8只用了2次扰动处理=1次位运算+1次异或),然后和(数组长度-1)做异或运算,得出数组下标;

、当table中的元素个数大于阈值(capacity * loadfactor)时,容器会进行扩容resize操作,将table数组大小扩充为2倍;

i.如果key的hash值对应的table下标元素为空,说明还没有元素,则直接插入,若不为空,则说明存在元素,发生了hash冲突,接下来要遍历链表或红黑树进行匹配查找;

ii.如果遍历的过程中,==或equals返回true,说明找到了对应的对象,直接更新该对象的value,返回旧value;

iii. 如果遍历结束,==或equals还是返回false,说明没有找到了对应的对象,则需要在该链表或红黑树中插入(JDK1.7使用头插法,JDK1.8 使用尾插法);

注:JDK1.8当链表长度大于等于8时,会把链表转换成红黑树;当红黑树深度小于等于6时,会把红黑树转换成链表。

get() 方法获取对象:

、和put方法第一步一样,通过调用hash(key.hashCode()) 方法,然后和(数组长度-1)做异或运算,得出数组下标;

、顺序遍历链表或红黑树,==或equals返回true,则返回对应的value值,否则返回null。

3、如何解决hash冲突?

通过异或运算能够是的计算出来的hash比较均匀,不容易出现冲突。但是偏偏出现了冲突现象,这时候该如何去解决呢?

在数据结构中,我们处理hash冲突常使用的方法有:开发定址法、再哈希法、链地址法、建立公共溢出区。而hashMap中处理hash冲突的方法就是链地址法。

这种方法的基本思想是将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。

4、为什么HashMap中table数组用transient修饰?

transient 表示易变的意思,在 Java 中,被该关键字修饰的变量不会被默认的序列化机制序列化。我们再回到源码中,考虑一个问题:桶数组 table 是 HashMap 底层重要的数据结构,不序列化的话,别人还怎么还原呢?

这里简单说明一下吧,HashMap 并没有使用默认的序列化机制,而是通过实现readObject/writeObject两个方法自定义了序列化的内容。这样做是有原因的,试问一句,HashMap 中存储的内容是什么?不用说,大家也知道是键值对。所以只要我们把键值对序列化了,我们就可以根据键值对数据重建 HashMap。有的朋友可能会想,序列化 table 不是可以一步到位,后面直接还原不就行了吗?这样一想,倒也是合理。但序列化 talbe 存在着两个问题:

1)table 多数情况下是无法被存满的,序列化未使用的部分,浪费空间。

2)同一个键值对在不同 JVM 下,所处的桶位置可能是不同的,在不同的 JVM 下反序列化 table 可能会发生错误。

以上两个问题中,第一个问题比较好理解,第二个问题解释一下。HashMap 的get/put/remove等方法第一步就是根据 hash 找到键所在的桶位置,但如果键没有覆写 hashCode 方法,计算 hash 时最终调用 Object 中的 hashCode 方法。但 Object 中的 hashCode 方法是 native 型的,不同的 JVM 下,可能会有不同的实现,产生的 hash 可能也是不一样的。也就是说同一个键在不同平台下可能会产生不同的 hash,此时再对在同一个 table 继续操作,就会出现问题。

综上所述,大家应该能明白 HashMap 不序列化 table 的原因了,下面是HashMap自定义的序列化代码:

5、为什么JDK1.8中不一下子把整个链表变为红黑树呢?

为什么非要等到链表的长度大于等于8的时候,才转变成红黑树?在这里可以从两方面来解释:

(1)put和remove过程中,红黑树要通过左旋,右旋、变色这些操作来保持平衡,另外构造红黑树要比构造链表复杂,在链表的节点不多的时候,从整体的性能看来, 数组+链表+红黑树的结构可能不一定比数组+链表的结构性能高。就好比杀鸡焉用牛刀的意思。

(2)HashMap频繁的扩容,会造成底部红黑树不断的进行拆分和重组,这是非常耗时的。因此,也就是链表长度比较长的时候转变成红黑树才会显著提高效率。

6、说一下红黑树?

由于二叉查找树(BST)存在数据倾斜的问题(极端情况下会形成一个链表),所以平衡二叉查找树(Balanced BST)产生了。平衡树在插入和删除的时候,会通过旋转操作将高度保持在logN。其中两款具有代表性的平衡树分别为AVL树和红黑树。AVL树由于实现比较复杂,而且插入和删除性能差,因此在实际环境中我们更多的是应用红黑树。

红黑树(Red-Black Tree)以下简称RBTree的实际应用非常广泛,比如Linux内核中的完全公平调度器、高精度计时器、ext3文件系统等等,各种语言的函数库如Java的TreeMap和TreeSet,C++ STL的map、multimap、multiset等,是函数式语言中最常用的持久数据结构之一。

由于在java8里HashMap的底层实现用RBTree取代链表,使得性能得到了提升,因此很多人才去关注或者深入学习红黑树。

红黑树特性,RBT树上的每个节点,都要遵循下面的规则:

每个节点都是红色或者黑色;

根节点必须始终是黑色;

没有两个相邻的红色节点;

对每个结点,从该结点到其子孙节点的所有路径上包含相同数目的黑结点。

RBT树在理论上还是一棵BST树,但是它在对BST的插入和删除操作时会维持树的平衡,即保证树的高度在[logN,logN+1](理论上,极端的情况下可以出现RBTree的高度达到2*logN,但实际上很难遇到)。这样RBTree的查找时间复杂度始终保持在O(logN)从而接近于理想的BST。RBTree的删除和插入操作的时间复杂度也是O(logN)。RBT的查找操作就是BST的查找操作。

7、为什么 ConcurrentHashMap 比 HashTable 效率要高?

HashTable 使用一把锁(锁住整个链表结构)处理并发问题,多个线程竞争一把锁,容易阻塞;

ConcurrentHashMap:

JDK 1.7 中使用分段锁(ReentrantLock + Segment + HashEntry),相当于把一个 HashMap 分成多个段,每段分配一把锁,这样支持多线程访问。锁粒度:基于 Segment,包含多个 HashEntry。

JDK 1.8 中使用 CAS + synchronized + Node + 红黑树。锁粒度:Node(首结点)(实现 Map.Entry)。锁粒度降低了。

8、HashMap 和 HashTable 有什么区别?

HashMap 是线程不安全的,HashTable 是线程安全的;

由于线程安全,所以 HashTable 的效率比不上 HashMap;

HashMap最多只允许一条记录的键为null,允许多条记录的值为null,而 HashTable不允许;

HashMap 默认初始化数组的大小为16,HashTable 为 11,前者扩容时,扩大两倍,后者扩大两倍+1;

HashMap 需要重新计算 hash 值,而 HashTable 直接使用对象的 hashCode

9、HashMap,LinkedHashMap,TreeMap 有什么区别?

HashMap:一般情况下使用最多,输出的顺序和输入的顺序不相同,在 Map 中插入、删除和定位元素时使用;

TreeMap:实现 SortMap 接口,能够把它保存的记录根据键排序(默认按键值升序排序,也可以指定排序的比较器),在需要按自然顺序或自定义顺序遍历键的情况下使用;

LinkedHashMap:保存了记录的插入顺序,在用 Iterator 遍历时,先取到的记录肯定是先插入的;遍历比 HashMap 慢;在需要输出的顺序和输入的顺序相同的情况下使用。

10、说一下JDK1.7 ConcurrentHashMap的并发度?

concurrencyLevel不能代表ConccurentHashMap实际并发度,ConccurentHashMap会使用大于等于该值的2的幂指数的最小值作为实际并发度,实际并发度即为segments数组的长度。创建时未指定concurrencyLevel则默认为16。

并发度对ConccurentHashMap性能具有举足轻重的作用,如果并发度设置的过小,会带来严重的锁竞争问题;如果并发度设置的过大,原本位于同一个Segment内的访问会扩散到不同的Segment中,CPU cache命中率会下降,从而引起程序性能下降。

欢迎小伙伴们留言交流~~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值