java 面试题 hashmap 是否线程安全

本文深入探讨了HashMap在并发场景下的问题,特别是在JDK1.8中的改进措施,包括红黑树的应用及其对性能的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

hashmap不适合并发的根本是他的put get没有做同步处理,死循环只是并发时会出现的一种较严重的问题,

据我所知1.8的hashmap引入了红黑树,但是默认hash链长度大于8时才会转成红黑树,关于并发支持并没有实质的改进


多线程并发情况下:

JDK1.8以前的版本;


1.Hashmap在插入元素过多的时候需要进行Resize,Resize的条件是

HashMap.Size   >=  Capacity * LoadFactor。


2.Hashmap的Resize包含扩容和ReHash两个步骤,ReHash在并发的情况下可能会形成链表环。



......................................

JDK1.7中

使用一个Entry数组来存储数据,用key的hashcode取模来决定key会被放到数组里的位置,如果hashcode相同,或者hashcode取模后的结果相同(hash collision),那么这些key会被定位到Entry数组的同一个格子里,这些key会形成一个链表。

在hashcode特别差的情况下,比方说所有key的hashcode都相同,这个链表可能会很长,那么put/get操作都可能需要遍历这个链表

也就是说时间复杂度在最差情况下会退化到O(n)

 

JDK1.8中

使用一个Node数组来存储数据,但这个Node可能是链表结构,也可能是红黑树结构

如果插入的key的hashcode相同,那么这些key也会被定位到Node数组的同一个格子里。

如果同一个格子里的key不超过8个,使用链表结构存储。

如果超过了8个,那么会调用treeifyBin函数,将链表转换为红黑树。

那么即使hashcode完全相同,由于红黑树的特点,查找某个特定元素,也只需要O(log n)的开销

也就是说put/get的操作的时间复杂度最差只有O(log n)

 

 

听起来挺不错,但是真正想要利用JDK1.8的好处,有一个限制:

key的对象,必须正确的实现了Compare接口


### 关于JavaHashMap的常见面试题及解答 #### 1. **什么是HashMap?其底层数据结构是什么?** HashMap 是基于哈希表的数据结构,允许存储键值对。它的核心工作原理是通过 `hashCode()` 方法计算键的哈希码,并将其映射到桶(bucket)的位置[^2]。 - 底层数据结构由数组和链表/红黑树组成,在 Java 8 中进行了优化,当某个桶中的节点数超过一定阈值(默认为 8),该桶会从链表转换为红黑树以提高查询效率。 #### 2. **为什么要在HashMap中重写`equals()`和`hashCode()`方法?** 为了确保自定义对象作为键时能够正常工作。如果两个对象相等,则它们的哈希码应该相同;反之则不一定成立。因此,`hashCode()` 和 `equals()` 的一致性对于 HashMap 正确运行至关重要[^1]。 以下是示例代码展示如何正确实现这两个方法: ```java class Student { private String id; private String name; @Override public int hashCode() { return Objects.hash(id, name); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Student student = (Student) obj; return Objects.equals(id, student.id) && Objects.equals(name, student.name); } } ``` #### 3. **HashMap 如何处理冲突?** 当多个键被分配到同一个桶时会发生冲突。HashMap 使用拉链法解决冲突——即在同一桶中维护一个链表或红黑树来保存这些键值对。在 Java 8 及之后版本中,当链表长度达到指定阈值(默认为 8)时,链表会被替换为红黑树以提升性能[^2]。 #### 4. **HashMap 的初始容量和加载因子的作用是什么?** - **初始容量**:创建 HashMap 实例时设定的大小,默认为 16。 - **加载因子**:表示 HashMap 能够容纳多少比例的元素而不触发扩容操作,默认为 0.75。 实际容量等于 `initialCapacity * loadFactor`。一旦超出此范围,HashMap 将自动调整内部数组大小并重新分布所有条目。 模拟扩容过程如下所示: ```java public class HashMapResizeDemo { public static void main(String[] args) { // 初始容量4,负载因子0.75,阈值3 HashMap<String, Integer> map = new HashMap<>(4, 0.75f); map.put("A", 1); map.put("B", 2); map.put("C", 3); // 触发扩容 System.out.println("扩容后容量:" + map.size()); } } ``` #### 5. **遍历 HashMap 的方式有哪些?** 可以通过多种方式进行迭代访问: - KeySet 遍历 - EntrySet 遍历 - Values 遍历 具体实现可参见以下代码片段: ```java import java.util.HashMap; import java.util.Map; public class HashMapTraversalExample { public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); map.put("Alice", 90); map.put("Bob", 85); // KeySet 遍历 for (String key : map.keySet()) { System.out.println(key + ": " + map.get(key)); } // EntrySet 遍历 for (Map.Entry<String, Integer> entry : map.entrySet()) { System.out.println(entry.getKey() + "->" + entry.getValue()); } // Values 遍历 for (Integer value : map.values()) { System.out.println(value); } } } ``` #### 6. **HashMap 和 Hashtable 的主要区别是什么?** | 特性 | HashMap | Hashtable | |-------------------|-------------------------------------------|------------------------------------| | 线程安全性 | 不安全 | 安全 | | Null 键和值支持 | 支持 | 不支持 | | 性能 | 更高效 | 较低 | 更多细节可见参考资料[^4]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值