写点什么

一文搞懂所有 HashMap 面试题

用户头像
云流
关注
发布于: 2020 年 11 月 26 日

HashMap 在 JDK1.7 和 JDK1.8 中有哪些不同?HashMap 的底层实现


  • JDK1.7 的底层数据结构(数组+链表)


在这里插入图片描述

  • JDK1.8 的底层数据结构(数组+链表)


在这里插入图片描述

  • JDK1.7 的 Hash 函数

  • JDK1.8 的 Hash 函数


HashMap 在 JDK1.7 和 JDK1.8 中有哪些不同点:


JDK1.7JDK1.8JDK1.8 的优势底层结构数组+链表数组+链表/红黑树(链表大于 8)避免单条链表过长而影响查询效率,提高查询效率 hash 值计算方式 9 次扰动 = 4 次位运算 + 5 次异或运算 2 次扰动 = 1 次位运算 + 1 次异或运算可以均匀地把之前的冲突的节点分散到新的桶(具体细节见下面扩容部分)插入数据方式头插法(先讲原位置的数据移到后 1 位,再插入数据到该位置)尾插法(直接插入到链表尾部/红黑树)解决多线程造成死循环地问题扩容后存储位置的计算方式重新进行 hash 计算原位置或原位置+旧容量省去了重新计算 hash 值的时间


HashMap 的长度为什么是 2 的幂次方


因为HashMap是通过key的 hash 值来确定存储的位置,但 Hash 值的范围是-2147483648 到 2147483647,不可能建立一个这么大的数组来覆盖所有 hash 值。所以在计算完 hash 值后会对数组的长度进行取余操作,如果数组的长度是 2 的幂次方,(length - 1)&hash等同于hash%length,可以用(length - 1)&hash这种位运算来代替 %取余的操作进而提高性能。


HashMap 的 put 方法的具体流程?


HashMap 的主要流程可以看下面这个流程图,逻辑非常清晰。


在这里插入图片描述

HashMap 的扩容操作是怎么实现的?


  • 初始值为 16,负载因子为 0.75,阈值为负载因子*容量

  • resize()方法是在hashmap中的键值对大于阀值时或者初始化时,就调用resize()方法进行扩容。

  • 每次扩容,容量都是之前的两倍

  • 扩容时有个判断e.hash & oldCap是否为零,也就是相当于 hash 值对数组长度的取余操作,若等于 0,则位置不变,若等于 1,位置变为原位置加旧容量。


HashMap 默认加载因子为什么选择 0.75?


这个主要是考虑空间利用率和查询成本的一个折中。如果加载因子过高,空间利用率提高,但是会使得哈希冲突的概率增加;如果加载因子过低,会频繁扩容,哈希冲突概率降低,但是会使得空间利用率变低。具体为什么是 0.75,不是 0.74 或 0.76,这是一个基于数学分析(泊松分布)和行业规定一起得到的一个结论。


为什么要将链表中转红黑树的阈值设为 8?为什么不一开始直接使用红黑树?


可能有很多人会问,既然红黑树性能这么好,为什么不一开始直接使用红黑树,而是先用链表,链表长度大于 8 时,才转换为红红黑树。


  • 因为红黑树的节点所占的空间是普通链表节点的两倍,但查找的时间复杂度低,所以只有当节点特别多时,红黑树的优点才能体现出来。至于为什么是 8,是通过数据分析统计出来的一个结果,链表长度到达 8 的概率是很低的,综合链表和红黑树的性能优缺点考虑将大于 8 的链表转化为红黑树。

  • 链表转化为红黑树除了链表长度大于 8,还要HashMap中的数组长度大于 64。也就是如果HashMap长度小于 64,链表长度大于 8 是不会转化为红黑树的,而是直接扩容。


HashMap 是怎么解决哈希冲突的?


哈希冲突:hashMap在存储元素时会先计算key的 hash 值来确定存储位置,因为key的 hash 值计算最后有个对数组长度取余的操作,所以即使不同的key也可能计算出相同的 hash 值,这样就引起了 hash 冲突。hashMap的底层结构中的链表/红黑树就是用来解决这个问题的。


HashMap中的哈希冲突解决方式可以主要从三方面考虑(以 JDK1.8 为背景)


  • 拉链法

  • hash 函数

  • 红黑树


HashMap 为什么不直接使用 hashCode()处理后的哈希值直接作为 table 的下标?


hashCode()处理后的哈希值范围太大,不可能在内存建立这么大的数组。


能否使用任何类作为 Map 的 key?


可以,但要注意以下两点:


  • 如果类重写了 equals() 方法,也应该重写hashCode()方法。

  • 最好定义key类是不可变的,这样key对应的hashCode() 值可以被缓存起来,性能更好,这也是为什么String特别适合作为HashMapkey


为什么 HashMap 中 String、Integer 这样的包装类适合作为 Key?


  • 这些包装类都是final修饰,是不可变性的, 保证了key的不可更改性,不会出现放入和获取时哈希值不同的情况。

  • 它们内部已经重写过hashcode(),equal()等方法。


如果使用 Object 作为 HashMap 的 Key,应该怎么办呢?


  • 重写hashCode()方法,因为需要计算 hash 值确定存储位置

  • 重写equals()方法,因为需要保证key的唯一性。


HashMap 多线程导致死循环问题


由于 JDK1.7 的hashMap遇到 hash 冲突采用的是头插法,在多线程情况下会存在死循环问题,但 JDK1.8 已经改成了尾插法,不存在这个问题了。但需要注意的是 JDK1.8 中的HashMap仍然是不安全的,在多线程情况下使用仍然会出现线程安全问题。基本上面试时说到这里既可以了,具体流程用口述是很难说清的,感兴趣的可以看这篇文章。HASHMAP的死循环


ConcurrentHashMap 底层具体实现知道吗?


  • JDK1.7


在这里插入图片描述

  • JDK1.8


在这里插入图片描述

总结一下:


  • JDK1.7 底层是ReentrantLock+Segment+HashEntry,JDK1.8 底层是synchronized+CAS+链表/红黑树

  • JDK1.7 采用的是分段锁,同时锁住几个HashEntry,JDK1.8 锁的是 Node 节点,只要没有发生哈希冲突,就不会产生锁的竞争。所以 JDK1.8 相比于 JDK1.7 提供了一种粒度更小的锁,减少了锁的竞争,提高了ConcurrentHashMap的并发能力。


HashTable 的底层实现知道吗?


HashTable的底层数据结构是数组+链表,链表主要是为了解决哈希冲突,并且整个数组都是synchronized修饰的,所以HashTable是线程安全的,但锁的粒度太大,锁的竞争非常激烈,效率很低。


在这里插入图片描述

HashMap、ConcurrentHashMap 及 Hashtable 的区别 | | HashMap(JDK1.8) | ConcurrentHashMap(JDK1.8) | Hashtable |


| :-: | :-: | :-: | :-: |

| 底层实现 | 数组+链表/红黑树 | 数组+链表/红黑树 | 数组+链表 |

| 线程安全 | 不安全 | 安全(Synchronized修饰 Node 节点) | 安全(Synchronized修饰整个表) |

| 效率 | 高 | 较高 | 低 |

| 扩容 | 初始 16,每次扩容成 2n | 初始 16,每次扩容成 2n | 初始 11,每次扩容成 2n+1 |

| 是否支持 Null key 和 Null Value | 可以有一个 Null key,Null Value 多个 | 不支持 | 不支持 |


Map 的常用方法


这些常用方法是需要背下来的,虽然面试用不上,但是笔试或者面试写算法题时会经常用到。


方法void clear()清除集合内的元素boolean containsKey(Object key)查询 Map 中是否包含指定 key,如果包含则返回 trueSet entrySet()返回 Map 中所包含的键值对所组成的 Set 集合,每个集合元素都是 Map.Entry 的对象Object get(Object key)返回 key 指定的 value,若 Map 中不包含 key 返回 nullboolean isEmpty()查询 Map 是否为空,若为空返回 trueSet keySet()返回 Map 中所有 key 所组成的集合Object put(Object key,Object value)添加一个键值对,如果已有一个相同的 key,则新的键值对会覆盖旧的键值对,返回值为覆盖前的 value 值,否则为 nullvoid putAll(Map m)将制定 Map 中的键值对复制到 Map 中Object remove(Object key)删除指定 key 所对应的键值对,返回所关联的 value,如果 key 不存在返回 nullint size()返回 Map 里面的键值对的个数Collection values()返回 Map 里所有 values 所组成的 Collectionboolean containsValue ( Object value)判断映像中是否存在值 value


作者:路人 zhang

链接:https://juejin.cn/post/6899339640730632206


用户头像

云流

关注

还未添加个人签名 2020.09.02 加入

还未添加个人简介

评论

发布
暂无评论
一文搞懂所有HashMap面试题