关于集合HashMap面试常问问题合集

1. **HashMap的底层原理**:
   - HashMap基于哈希表实现,它通过哈希函数将键映射到哈希表的桶(数组)中。当你存储一个键值对时,HashMap会根据键的哈希码找到对应的桶,并在桶中存储该键值对。如果发生哈希冲突,即两个键具有相同的哈希码,它们会被存储在同一个桶中的链表或红黑树中。

2. **HashMap底层为什么要用红黑树呢?为什么不用平衡二叉树?**:
   - 红黑树是一种特殊的自平衡二叉查找树,在绝大多数情况下,红黑树的性能优于平衡二叉树。HashMap在JDK8中引入了红黑树来优化在发生哈希冲突时链表过长的情况,将链表转换为红黑树可以将查找、插入和删除的时间复杂度从O(n)降低到O(log n),提高了性能。

3. **HashMap啥时候扩容,为什么扩容**:
   - 当HashMap中的元素数量超过了负载因子(默认为0.75倍HashMap的容量)与当前容量的乘积时,HashMap会进行扩容操作。扩容的目的是为了减少哈希冲突,提高HashMap的性能。

4. **如果两个同样的put相同的key,会发生什么?**:
   - 如果你对同一个HashMap对象使用`put`方法两次,使用相同的键但不同的值,那么第二次的值将覆盖第一次的值。如果两次`put`使用相同的键和相同的值,则第二次操作不会对HashMap产生任何影响,因为键已经存在。

5. **HashMap是线程安全的吗?为什么呢?**:
   - 不,HashMap不是线程安全的。HashMap的实现不是同步的,因此在多线程环境中使用HashMap可能会导致不确定的行为或数据损坏。如果需要在多线程环境中使用HashMap,可以考虑使用`Collections.synchronizedMap()`方法或`ConcurrentHashMap`类。

6. **HashMap在遇到key冲突的时候是怎么处理的呢?**:
   - 当HashMap遇到键冲突时,即两个键具有相同的哈希码但不同的值,它会将这些键值对存储在同一个桶中的链表或红黑树中。这种处理方式被称为“开放地址法”或“拉链法”。

7. **HashMap put get过程**:
   - `put`过程:根据键的哈希码找到对应的桶,如果桶为空则直接插入,如果不为空则判断是否已经存在相同的键,如果存在则更新值,如果不存在则插入到链表或红黑树中。`get`过程:根据键的哈希码找到对应的桶,然后在链表或红黑树中查找对应的值。

8. **取模的时候为什么用&(length-1)**:
   - 这是因为HashMap的容量总是2的幂次方,使用位运算取模操作(`&`运算)比使用取余操作(`%`运算)效率更高。而对于2的幂次方的值,`(length - 1)`的二进制表示形式都是以所有位为1的形式,这样与操作就相当于对哈希值取模`length`,且性能更高。

9. **HashMap的底层数据结构?链表长度大于8就会转化成红黑树吗,没有对数组的插入有要求吗?链表是双向链表还是单向链表?**:
   - HashMap的底层数据结构是数组加链表/红黑树。在JDK8中,当链表长度超过8时,链表会被转换为红黑树,以提高查找、插入和删除操作的性能。对于数组的插入并没有严格的要求,因为HashMap采用了拉链法来处理哈希冲突,即使数组中有多个元素映射到同一个桶,它们仍然可以以链表的形式存储在同一个桶中。至于链表的实现,是采用单向链表还是双向链表,HashMap并没有做出规定,一般来说是单向链表,但具体实现可能因不同的JDK版本而异。

10. **HashMap什么时候退回回链表?为什么不是7**:
    - 当HashMap的容量小于64时,即数组的长度小于64时,即使链表长度超过了8,也不会将链表转换为红黑树。这是因为在较小的数组中,红黑树的性能可能不如链表,所以为了避免性能下降,HashMap在数组长度小于64时不会将链表转换为红黑树。

11. **HashMap不是线程安全的,如果要保证线程安全怎么办呢?可以用什么?**:
    - 如果需要在多线程环境中使用HashMap,并且需要保证线程安全,可以使用`ConcurrentHashMap`类。`ConcurrentHashMap`是Java并发包中提供的线程安全的哈希表实现,它通过分段锁(Segment)来实现并发访问,不同的段可以同时被不同的线程操作,从而提高了并发性能。

12. **有哪些线程安全的集合类,讲一讲原理(HashTable,ConcurrentHashMap,CopyOnWriteArrayList)**:
    - `HashTable`是Java早期提供的线程安全的哈希表实现,它在方法上加上了`synchronized`关键字来实现同步,但它的性能不如`ConcurrentHashMap`。
    - `ConcurrentHashMap`采用了分段锁的机制来实现并发访问,将整个Map分成多个段(Segment),每个段拥有自己的锁,不同的段可以同时被不同的线程操作,从而提高了并发性能。
    - `CopyOnWriteArrayList`是一个线程安全的动态数组实现,它通过在写操作(add、set等)时复制整个数组来实现线程安全,因此写操作的性能相对较低,但读操作的性能很高,适合读多写少的场景。

13. **HashMap一套put流程,1.8的改进(红黑树,头插变尾插,扩容等)**:
    - 在HashMap中执行`put`操作时,首先计算键的哈希码,然后根据哈希码找到对应的桶(数组索引),如果桶为空,则直接在该位置插入键值对;如果桶不为空,则遍历桶中的链表或红黑树,判断键是否已经存在,如果存在则更新值,如果不存在则将键值对插入到链表或红黑树中。在JDK1.8中,HashMap做了一些优化:
        - 当插入的键在桶中已经存在时,1.8版本采用了尾插法,即将新的键值对插入到链表或红黑树的末尾,而不是头插法,这样可以保持链表或红黑树中键值对的插入顺序,避免了扩容后链表顺序逆转导致的死循环。
        - 在链表长度达到一定阈值(默认为8)时,将链表转换为红黑树,以提高查找、插入和删除操作的性能。
        - 扩容时采用了二次幂扩容,而不是原来的增加一倍容量,这样在进行取模操作时可以直接用位运算,提高了性能。

14. **HashMap、ConcurrentHashMap的原理以及区别(要求从源码上解释)**:
    - HashMap是非线程安全的哈希表实现,底层数据结构是数组+链表/红黑树。在1.8版本中,引入了红黑树来优化链表过长的情况,提高了性能。
    - ConcurrentHashMap是线程安全的哈希表实现,底层也是数组+链表/红黑树,但相比HashMap,ConcurrentHashMap使用了分段锁的机制来实现并发访问。在源码中,ConcurrentHashMap通过`Segment`类实现了分段锁,每个Segment类似于一个小的HashMap,具有自己的锁,不同的Segment可以同时被不同的线程操作,从而提高了并发性能。

18. **讲一讲HashSet、Hashtable**:
    - `HashSet`是基于HashMap实现的,它底层使用HashMap来存储数据,只不过将HashMap的value部分设为了一个固定的对象,所有的值都映射到同一个对象上,从而实现了无重复元素的集合。
    - `Hashtable`是Java早期提供的线程安全的哈希表实现,它在方法上加上了`synchronized`关键字来实现同步,从而保证了多线程环境下的线程安全性。然而,由于其使用了全局锁,性能较差,已经被`ConcurrentHashMap`所取代。

  • 23
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值