关于java:HashMap面试题

49次阅读

共计 4918 个字符,预计需要花费 13 分钟才能阅读完成。

1:HashMap 的数据结构?

A:哈希表构造(链表散列:数组 + 链表)实现,联合数组和链表的长处。当链表长度超过 8 时,链表转换为红黑树。

transient Node<K,V>[] table; 

2:HashMap 的工作原理?

HashMap 底层是 hash 数组和单向链表实现,数组中的每个元素都是链表,由 Node 外部类(实现 Map.Entry 接口)实现,HashMap 通过 put & get 办法存储和获取。

存储对象时,将 K/V 键值传给 put() 办法:

①、调用 hash(K) 办法计算 K 的 hash 值,而后联合数组长度,计算得数组下标;

②、调整数组大小(当容器中的元素个数大于 capacity * loadfactor 时,容器会进行扩容 resize 为 2n);

③、i. 如果 K 的 hash 值在 HashMap 中不存在,则执行插入,若存在,则产生碰撞;

ii. 如果 K 的 hash 值在 HashMap 中存在,且它们两者 equals 返回 true,则更新键值对;

iii. 如果 K 的 hash 值在 HashMap 中存在,且它们两者 equals 返回 false,则插入链表的尾部(尾插法)或者红黑树中(树的增加形式)。

(JDK 1.7 之前应用头插法、JDK 1.8 应用尾插法)(留神:当碰撞导致链表大于 TREEIFY_THRESHOLD = 8 时,就把链表转换成红黑树)

获取对象时,将 K 传给 get() 办法:①、调用 hash(K) 办法(计算 K 的 hash 值)从而获取该键值所在链表的数组下标;②、程序遍历链表,equals()办法查找雷同 Node 链表中 K 值对应的 V 值。

hashCode 是定位的,存储地位;equals 是定性的,比拟两者是否相等。

3. 当两个对象的 hashCode 雷同会产生什么?

因为 hashCode 雷同,不肯定就是相等的(equals 办法比拟),所以两个对象所在数组的下标雷同,” 碰撞 ” 就此产生。又因为 HashMap 应用链表存储对象,这个 Node 会存储到链表中。为什么要重写 hashcode 和 equals 办法?举荐看下。

4. 你晓得 hash 的实现吗?为什么要这样实现?

JDK 1.8 中,是通过 hashCode() 的高 16 位异或低 16 位实现的:(h = k.hashCode()) ^ (h >>> 16),次要是从速度,效用和品质来思考的,缩小零碎的开销,也不会造成因为高位没有参加下标的计算,从而引起的碰撞。

5. 为什么要用异或运算符?

保障了对象的 hashCode 的 32 位值只有有一位产生扭转,整个 hash() 返回值就会扭转。尽可能的缩小碰撞。

6.HashMap 的 table 的容量如何确定?loadFactor 是什么?该容量如何变动?这种变动会带来什么问题?

①、table 数组大小是由 capacity 这个参数确定的,默认是 16,也能够结构时传入,最大限度是 1 <<30;

②、loadFactor 是装载因子,次要目标是用来确认 table 数组是否须要动静扩大,默认值是 0.75,比方 table 数组大小为 16,装载因子为 0.75 时,threshold 就是 12,当 table 的理论大小超过 12 时,table 就须要动静扩容;

③、扩容时,调用 resize() 办法,将 table 长度变为原来的两倍(留神是 table 长度,而不是 threshold)

④、如果数据很大的状况下,扩大时将会带来性能的损失,在性能要求很高的中央,这种损失很可能很致命。

7.HashMap 中 put 办法的过程?

答:“调用哈希函数获取 Key 对应的 hash 值,再计算其数组下标;

如果没有呈现哈希抵触,则间接放入数组;如果呈现哈希抵触,则以链表的形式放在链表前面;

如果链表长度超过阀值(TREEIFY THRESHOLD==8),就把链表转成红黑树,链表长度低于 6,就把红黑树转回链表;

如果结点的 key 曾经存在,则替换其 value 即可;

如果汇合中的键值对大于 12,调用 resize 办法进行数组扩容。”

8. 数组扩容的过程?

创立一个新的数组,其容量为旧数组的两倍,并从新计算旧数组中结点的存储地位。结点在新数组中的地位只有两种,原下标地位或原下标 + 旧数组的大小。

9. 拉链法导致的链表过深问题为什么不必二叉查找树代替,而抉择红黑树?为什么不始终应用红黑树?

之所以抉择红黑树是为了解决二叉查找树的缺点,二叉查找树在非凡状况下会变成一条线性构造(这就跟原来应用链表构造一样了,造成很深的问题),遍历查找会十分慢。举荐:面试问红黑树,我脸都绿了。

而红黑树在插入新数据后可能须要通过左旋,右旋、变色这些操作来保持平衡,引入红黑树就是为了查找数据快,解决链表查问深度的问题,咱们晓得红黑树属于均衡二叉树,然而为了放弃“均衡”是须要付出代价的,然而该代价所损耗的资源要比遍历线性链表要少,所以当长度大于 8 的时候,会应用红黑树,如果链表长度很短的话,基本不须要引入红黑树,引入反而会慢。

10. 说说你对红黑树的见解?

  • 每个节点非红即黑
  • 根节点总是彩色的
  • 如果节点是红色的,则它的子节点必须是彩色的(反之不肯定)
  • 每个叶子节点都是彩色的空节点(NIL 节点)
  • 从根节点到叶节点或空子节点的每条门路,必须蕴含雷同数目的彩色节点(即雷同的彩色高度)

11.jdk8 中对 HashMap 做了哪些扭转?

在 java 1.8 中,如果链表的长度超过了 8,那么链表将转换为红黑树。(桶的数量必须大于 64,小于 64 的时候只会扩容)

产生 hash 碰撞时,java 1.7 会在链表的头部插入,而 java 1.8 会在链表的尾部插入

在 java 1.8 中,Entry 被 Node 代替(换了一个马甲。

12.HashMap,LinkedHashMap,TreeMap 有什么区别?

HashMap 参考其余问题;

LinkedHashMap 保留了记录的插入程序,在用 Iterator 遍历时,先取到的记录必定是先插入的;遍历比 HashMap 慢;

TreeMap 实现 SortMap 接口,可能把它保留的记录依据键排序(默认按键值升序排序,也能够指定排序的比拟器)

13.HashMap & TreeMap & LinkedHashMap 应用场景?

个别状况下,应用最多的是 HashMap。

HashMap:在 Map 中插入、删除和定位元素时;

TreeMap:在须要按天然程序或自定义程序遍历键的状况下;

LinkedHashMap:在须要输入的程序和输出的程序雷同的状况下。

14.HashMap 和 HashTable 有什么区别?

①、HashMap 是线程不平安的,HashTable 是线程平安的;

②、因为线程平安,所以 HashTable 的效率比不上 HashMap;

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

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

⑤、HashMap 须要从新计算 hash 值,而 HashTable 间接应用对象的 hashCode

15.Java 中的另一个线程平安的与 HashMap 极其相似的类是什么?同样是线程平安,它与 HashTable 在线程同步上有什么不同?

ConcurrentHashMap 类(是 Java 并发包 java.util.concurrent 中提供的一个线程平安且高效的 HashMap 实现)。

HashTable 是应用 synchronize 关键字加锁的原理(就是对对象加锁);

而针对 ConcurrentHashMap,在 JDK 1.7 中采纳 分段锁的形式;JDK 1.8 中间接采纳了 CAS(无锁算法)+ synchronized。

16.HashMap & ConcurrentHashMap 的区别?

除了加锁,原理上无太大区别。另外,HashMap 的键值对容许有 null,然而 ConCurrentHashMap 都不容许。

17. 为什么 ConcurrentHashMap 比 HashTable 效率要高?

HashTable 应用一把锁(锁住整个链表构造)解决并发问题,多个线程竞争一把锁,容易阻塞;

ConcurrentHashMap

  • JDK 1.7 中应用分段锁(ReentrantLock + Segment + HashEntry),相当于把一个 HashMap 分成多个段,每段调配一把锁,这样反对多线程拜访。锁粒度:基于 Segment,蕴含多个 HashEntry。
  • JDK 1.8 中应用 CAS + synchronized + Node + 红黑树。锁粒度:Node(首结
    点)(实现 Map.Entry)。锁粒度升高了。

18. 针对 ConcurrentHashMap 锁机制具体分析(JDK 1.7 VS JDK 1.8)

JDK 1.7 中,采纳分段锁的机制,实现并发的更新操作,底层采纳数组 + 链表的存储构造,包含两个外围动态外部类 Segment 和 HashEntry。

①、Segment 继承 ReentrantLock(重入锁)用来充当锁的角色,每个 Segment 对象守护每个散列映射表的若干个桶;

②、HashEntry 用来封装映射表的键 - 值对;

③、每个桶是由若干个 HashEntry 对象链接起来的链表

JDK 1.8 中,采纳 Node + CAS + Synchronized 来保障并发平安。勾销类 Segment,间接用 table 数组存储键值对;当 HashEntry 对象组成的链表长度超过 TREEIFY_THRESHOLD 时,链表转换为红黑树,晋升性能。底层变更为数组 + 链表 + 红黑树。

19.ConcurrentHashMap 在 JDK 1.8 中,为什么要应用内置锁 synchronized 来代替重入锁 ReentrantLock?

①、粒度升高了;

②、JVM 开发团队没有放弃 synchronized,而且基于 JVM 的 synchronized 优化空间更大,更加天然。

③、在大量的数据操作下,对于 JVM 的内存压力,基于 API 的 ReentrantLock 会开销更多的内存。

20.ConcurrentHashMap 简略介绍?

①、重要的常量:

private transient volatile int sizeCtl;

当为正数时,-1 示意正在初始化,-N 示意 N – 1 个线程正在进行扩容;

当为 0 时,示意 table 还没有初始化;

当为其余负数时,示意初始化或者下一次进行扩容的大小。

②、数据结构:

Node 是存储构造的根本单元,继承 HashMap 中的 Entry,用于存储数据;

TreeNode 继承 Node,然而数据结构换成了二叉树构造,是红黑树的存储构造,用于红黑树中存储数据;

TreeBin 是封装 TreeNode 的容器,提供转换红黑树的一些条件和锁的管制。

③、存储对象时(put() 办法):

如果没有初始化,就调用 initTable() 办法来进行初始化;

如果没有 hash 抵触就间接 CAS 无锁插入;

如果须要扩容,就先进行扩容;

如果存在 hash 抵触,就加锁来保障线程平安,两种状况:一种是链表模式就间接遍历

到尾端插入,一种是红黑树就依照红黑树结构插入;

如果该链表的数量大于阀值 8,就要先转换成红黑树的构造,break 再一次进入循环

如果增加胜利就调用 addCount() 办法统计 size,并且查看是否须要扩容。

④、扩容办法 transfer():默认容量为 16,扩容时,容量变为原来的两倍。

helpTransfer():调用多个工作线程一起帮忙进行扩容,这样的效率就会更高。

⑤、获取对象时(get()办法):

计算 hash 值,定位到该 table 索引地位,如果是首结点合乎就返回;

如果遇到扩容时,会调用标记正在扩容结点 ForwardingNode.find()办法,查找该结点,匹配就返回;

以上都不合乎的话,就往下遍历结点,匹配就返回,否则最初就返回 null。

21.ConcurrentHashMap 的并发度是什么?

程序运行时可能同时更新 ConccurentHashMap 且不产生锁竞争的最大线程数。默认为 16,且能够在构造函数中设置。

当用户设置并发度时,ConcurrentHashMap 会应用大于等于该值的最小 2 幂指数作为理论并发度(如果用户设置并发度为 17,理论并发度则为 32)

转载自:https://zhuanlan.zhihu.com/p/…

正文完
 0