Java集合面试总结(题目来源JavaGuide)
问题1:说说 List,Set,Map 三者的区别?
在 Java 中,List
、Set
和 Map
是最常用的集合框架(Collection Framework)接口,它们的主要区别如下:
1. List(列表)
-
特点:
- 允许存储 重复元素。
- 保持插入顺序,即元素的存储顺序与遍历顺序一致。
- 允许null值(可存多个)。
- 提供索引访问,可以使用索引(
get(int index)
)获取元素。
-
常见实现类:
ArrayList
(基于数组,查询快,增删慢)LinkedList
(基于双向链表,查询慢,增删快)Vector
(线程安全的ArrayList
,但性能较低)
-
使用场景:
- 需要有序存储元素,并且可能有重复值,例如存储一系列日志记录、待办事项等。
2. Set(集合)
-
特点:
- 不允许存储重复元素。
- 无法通过索引访问元素(没有
get(int index)
方法)。 - 默认情况下不保证元素的存储顺序(
TreeSet
除外)。 - 允许最多一个 null 值(
HashSet
允许,TreeSet
不允许)。
-
常见实现类:
HashSet
(基于哈希表,存取快,但无序)LinkedHashSet
(有序的 HashSet,按插入顺序存储)TreeSet
(基于红黑树,自动排序,不允许null
)
-
使用场景:
- 需要存储唯一的元素集合,例如存储用户ID、标签等。
3. Map(映射)
-
特点:
- 以 键值对 (key-value) 形式存储数据。
- Key 不可重复,但 Value 可以重复。
- 允许最多一个
null
键,多值null
值(TreeMap
不允许null
键)。
-
常见实现类:
HashMap
(无序,基于哈希表,存取快,线程不安全)LinkedHashMap
(有序的 HashMap,按插入顺序存储)TreeMap
(排序的 Map,基于红黑树,按 Key 排序)
-
使用场景:
- 需要存储键值映射,如学生学号-姓名、配置项键值对等。
总结对比
特性 | List | Set | Map |
---|---|---|---|
是否允许重复元素 | 允许 | 不允许 | Key 不允许重复,Value 允许 |
是否保证顺序 | 保证插入顺序 | HashSet 无序,LinkedHashSet 保证插入顺序,TreeSet 排序 | HashMap 无序,LinkedHashMap 保持插入顺序,TreeMap 按 key 排序 |
是否可存 null | 允许多个 | 仅 HashSet 允许一个 | HashMap 允许一个 null Key,多 null Value,TreeMap 不允许 null Key |
访问方式 | 通过索引 (get(int index) ) | 迭代器 (Iterator ) | 通过 Key (get(K key) ) |
适用场景 | 需要有序存储并允许重复,如购物车、任务列表 | 需要唯一元素,如用户ID | 需要键值映射,如用户信息 |
简单记忆:
List
适合 有序、可重复 场景。Set
适合 唯一、无序(或排序) 场景。Map
适合 键值映射 场景。
问题2: List,Set,Map 在 Java 中分别由哪些对应的实现类?底层的数据结构?
在 Java 中,List
、Set
和 Map
各自有多个常见的实现类,它们的底层数据结构不同,适用于不同的场景。下面详细分析各自的实现类及其底层结构。
1. List(列表)
List
主要有以下几个实现类:
(1)ArrayList
-
底层数据结构:动态数组
-
特点:
-
查询速度快(基于数组,通过索引访问,
O(1)
)。 -
插入和删除慢(涉及数据移动,
O(n)
)。 -
允许
null
元素,允许重复元素。 -
线程不安全。
-
-
适用场景:
-
读操作多,写操作少的场景,如缓存数据、搜索结果等。
-
(2)LinkedList
-
底层数据结构:双向链表
-
特点:
-
查询速度慢(链表遍历,
O(n)
)。 -
插入、删除速度快(
O(1)
,不涉及数据移动)。 -
允许
null
元素,允许重复元素。 -
线程不安全。
-
-
适用场景:
-
频繁插入、删除元素,如任务队列、消息队列等。
-
(3)Vector
-
底层数据结构:动态数组
-
特点:
-
与
ArrayList
类似,但线程安全(方法加了synchronized
)。 -
查询快,插入删除慢。
-
允许
null
元素,允许重复元素。
-
-
适用场景:
-
多线程环境下,需要线程安全的
List
。
-
2. Set(集合)
Set
主要有以下几个实现类:
(1)HashSet
-
底层数据结构:哈希表(基于
HashMap
实现,值存HashMap
的 key) -
特点:
-
无序存储元素(元素顺序可能变化)。
-
不允许重复元素。
-
允许
null
值(但只能有一个)。 -
查询、插入、删除速度快(平均
O(1)
)。
-
-
适用场景:
-
需要去重的集合,如存储唯一用户ID。
-
(2)LinkedHashSet
-
底层数据结构:哈希表 + 双向链表
-
特点:
-
有序(按照插入顺序存储)。
-
不允许重复元素。
-
查询、插入、删除速度比
HashSet
略慢(受链表影响)。
-
-
适用场景:
-
既需要去重,又要保持插入顺序的场景,如LRU缓存。
-
(3)TreeSet
-
底层数据结构:红黑树(自平衡二叉搜索树)
-
特点:
-
自动排序(默认按升序排序,可自定义
Comparator
)。 -
不允许重复元素。
-
不允许
null
。 -
查询、插入、删除速度
O(log n)
(比HashSet
慢)。
-
-
适用场景:
-
需要排序且去重的集合,如排行榜、时间排序数据。
-
3. Map(映射)
Map
主要有以下几个实现类:
(1)HashMap
-
底层数据结构:数组 + 单链表 + 红黑树(JDK 1.8 之后)
-
特点:
-
Key
无序存储,不允许重复,允许null
(最多一个)。 -
Value
允许重复,允许null
。 -
查询、插入、删除快(
O(1)
,最坏O(log n)
)。 -
线程不安全。
-
-
适用场景:
-
需要快速查找
key-value
数据,如缓存、配置项存储。
-
(2)LinkedHashMap
-
底层数据结构:哈希表 + 双向链表
-
特点:
-
按插入顺序存储(可以用
accessOrder=true
变成 LRU 访问顺序)。 -
查询、插入、删除速度略低于
HashMap
(受链表影响)。
-
-
适用场景:
-
需要按插入顺序遍历的
Map
,如缓存实现。
-
(3)TreeMap
-
底层数据结构:红黑树
-
特点:
-
Key 自动排序(默认升序,可自定义
Comparator
)。 -
Key 不允许
null
(Value 允许)。 -
查询、插入、删除
O(log n)
。
-
-
适用场景:
-
需要按 key 排序的
Map
,如时间戳排序的日志数据。
-
(4)Hashtable
-
底层数据结构:哈希表
-
特点:
-
线程安全(方法加
synchronized
)。 -
不允许
null
key 和null
value。 -
查询、插入、删除
O(1)
。 -
效率较低(同步开销大)。
-
-
适用场景:
-
需要线程安全的
Map
(但通常用ConcurrentHashMap
替代)。
-
(5)ConcurrentHashMap
-
底层数据结构:分段锁 + 哈希表(JDK 1.7),CAS + 哈希表 + 红黑树(JDK 1.8)
-
特点:
-
线程安全,高效(分段锁 / CAS 机制)。
-
不允许
null
key 和null
value。 -
查询、插入、删除比
Hashtable
快(O(1)
)。
-
-
适用场景:
-
高并发环境下的
Map
,如缓存存储。
-
总结
类型 | 实现类 | 底层数据结构 | 主要特点 | 适用场景 |
---|---|---|---|---|
List |
| 动态数组 | 查询快,增删慢 | 读多写少 |
| 双向链表 | 插入/删除快,查询慢 | 频繁增删 | |
| 动态数组 | 线程安全 | 多线程 | |
Set |
| 哈希表 | 无序,不重复 | 唯一集合 |
| 哈希表 + 双向链表 | 插入顺序 | 唯一且有序 | |
| 红黑树 | 自动排序 | 唯一且排序 | |
Map |
| 数组 + 链表 + 红黑树 | 无序,查询快 | 快速查找 |
| 哈希表 + 双向链表 | 按插入顺序 | LRU缓存 | |
| 红黑树 | Key 自动排序 | 需要排序 | |
| 哈希表 | 线程安全,低效 | 线程安全(少用) | |
| 哈希表 + CAS | 高并发 | 并发环境 |
问题3:有哪些集合是线程不安全的?怎么解决呢?
1. 线程不安全的集合
(1)ArrayList(线程不安全)
-
底层数据结构:动态数组
-
问题:多线程环境下,多个线程同时
add()
可能导致 数据覆盖、数组扩容时的数据丢失。 -
解决方案:
-
使用
Vector
(线程安全,但性能较低) -
使用
Collections.synchronizedList(new ArrayList<>())
-
使用
CopyOnWriteArrayList
(适用于读多写少的场景)
-
(2)HashMap(线程不安全)
-
底层数据结构:数组 + 链表(JDK 1.7),数组 + 链表 + 红黑树(JDK 1.8)
-
问题:
-
JDK 1.7 多线程
put()
时可能引发死循环(rehash 过程中链表反转)。 -
JDK 1.8 仍然是线程不安全的,多线程
put()
可能会丢数据。
-
-
解决方案:
-
使用
ConcurrentHashMap
(高性能并发 Map,推荐) -
使用
Collections.synchronizedMap(new HashMap<>())
(性能较低) -
使用
Hashtable
(线程安全但性能差,较少使用)
-
2. ArrayList vs. Vector(高频对比)
对比项 |
|
|
---|---|---|
线程安全 | ❌ 非线程安全 | ✅ 线程安全(synchronized) |
底层数据结构 | 动态数组 | 动态数组 |
扩容方式 |
|
|
性能 | 高(无锁) | 低(同步开销大) |
适用场景 | 单线程 | 多线程(但 |
💡 为什么 Vector
不推荐?
-
Vector
的方法使用synchronized
关键字,导致所有方法都串行执行,性能较低。 -
多线程环境下,更推荐
CopyOnWriteArrayList
或Collections.synchronizedList(new ArrayList<>())
。
3. HashMap vs. ConcurrentHashMap(高频对比)
对比项 |
|
|
---|---|---|
线程安全 | ❌ 非线程安全 | ✅ 线程安全 |
底层数据结构 | 数组 + 链表(JDK 1.7) | JDK 1.7:分段锁 + 哈希表(Segment 机制) |
| ✅ 允许 | ❌ 不允许 |
并发性能 | 低(多线程可能出错) | 高(JDK 1.8 采用 CAS 机制,提高并发性能) |
适用场景 | 单线程 | 高并发环境 |
4. ConcurrentHashMap 如何保证线程安全?
🔹 JDK 1.7(Segment 分段锁)
-
ConcurrentHashMap
采用 Segment + HashEntry 结构,将整个 Map 分成多个Segment
(默认 16 个)。 -
每个
Segment
维护自己的锁,只有访问相同Segment
的线程才会竞争锁,大大提高并发性。 -
缺点:
Segment
结构较复杂,锁的粒度仍然较大。
🔹 JDK 1.8(CAS + synchronized
+ 红黑树)
-
取消了
Segment
,改为 数组 + 链表 + 红黑树 结构,与HashMap
结构类似。 -
核心优化:
-
CAS(Compare-And-Swap)机制:插入新元素时,先尝试用 CAS 方式写入,失败再加锁。
-
synchronized
代替ReentrantLock
:JDK 1.8 采用 Node 级别加锁,仅在 Hash 冲突时加锁,降低锁竞争。 -
红黑树优化:当链表长度超过 8 时,转换为红黑树,提高查询性能。
-
💡 为什么 ConcurrentHashMap
性能更好?
-
HashTable
是全表锁,每次访问都需要synchronized
,性能低。 -
ConcurrentHashMap
采用 CAS + 局部锁,大幅提升并发性能。
5. 线程安全的解决方案(汇总)
线程不安全集合 | 线程安全替代方案 |
---|---|
|
|
|
|
|
|
|
|
6. 总结
-
ArrayList
vs.Vector
-
ArrayList
非线程安全,性能高。 -
Vector
线程安全,但同步开销大,不推荐。 -
推荐:
CopyOnWriteArrayList
(读多写少)。
-
-
HashMap
vs.ConcurrentHashMap
-
HashMap
非线程安全,多线程环境下可能丢数据。 -
ConcurrentHashMap
采用 CAS +synchronized
保证线程安全,高并发推荐。
-
-
ConcurrentHashMap 如何保证线程安全?
-
JDK 1.7:
Segment
分段锁(16 个小锁)。 -
JDK 1.8:
CAS +
synchronized` + 红黑树(更高效)。
-
如果你在面试中被问到: 👉 ArrayList 和 Vector 的区别?
-
Vector 是同步的,ArrayList 不是,性能上 ArrayList 更好。
-
现在不推荐 Vector,而是使用
CopyOnWriteArrayList
。
👉 HashMap 和 ConcurrentHashMap 的区别?
-
HashMap
非线程安全,ConcurrentHashMap
线程安全,高并发推荐。 -
JDK 1.8
ConcurrentHashMap
采用 CAS +synchronized
,比 JDK 1.7 更高效。
问题4: HashMap 查询,删除的时间复杂度
1. HashMap 底层数据结构(JDK 1.8 之后)
HashMap
采用 数组 + 链表 + 红黑树 作为底层数据结构:
-
当没有哈希冲突时(即均匀分布的情况下):时间复杂度为
O(1)
。 -
当发生哈希冲突时:
-
链表存储:查找或删除需要遍历链表,最坏时间复杂度
O(n)
。 -
红黑树存储(链表长度 ≥ 8 时转换为红黑树):查询、删除的时间复杂度降为
O(log n)
。
-
2. HashMap 查询的时间复杂度
-
理想情况下(无哈希冲突):
-
通过
key
计算hash
值,找到数组索引,直接返回值,时间复杂度O(1)
。
-
-
最坏情况(所有
key
哈希冲突,存储为链表):-
需要遍历整个链表,时间复杂度
O(n)
。
-
-
优化后(链表转红黑树):
-
当链表长度 >= 8 时,自动转为红黑树,查询复杂度降为
O(log n)
。
-
✅ 最终查询时间复杂度:
-
平均情况:
O(1)
-
最坏情况(链表):
O(n)
-
最坏情况(红黑树):
O(log n)
示例:
Map<Integer, String> map = new HashMap<>();
map.put(1, "A");
map.put(2, "B");
String value = map.get(1); // O(1)(理想情况)
3. HashMap 删除的时间复杂度
-
理想情况下(无哈希冲突):
-
remove(key)
计算hash
,直接删除,时间复杂度O(1)
。
-
-
最坏情况(哈希冲突,链表存储):
-
需要遍历链表找到
key
并删除,时间复杂度O(n)
。
-
-
优化后(链表转红黑树):
-
删除操作
O(log n)
。
-
✅ 最终删除时间复杂度:
-
平均情况:
O(1)
-
最坏情况(链表):
O(n)
-
最坏情况(红黑树):
O(log n)
示例:
map.remove(1); // O(1)(理想情况)
4. 复杂度总结
操作 | 平均时间复杂度 | 最坏时间复杂度(链表) | 最坏时间复杂度(红黑树) |
---|---|---|---|
|
|
|
|
|
|
|
|
🔹 面试重点:
-
JDK 1.8 之前:查询、删除最坏情况
O(n)
(哈希冲突形成链表)。 -
JDK 1.8 之后:链表长度 ≥ 8 时转为红黑树,优化为
O(log n)
。
问题5: HashMap 的底层实现
JDK1.8 之前 : 数组和链表
JDK1.8 之后 : 多了红黑树
问题6: HashMap 的长度为什么是 2 的幂次方?
问题 | 答案 |
---|---|
为什么 HashMap 长度是 2 的幂? | 为了 优化索引计算,减少哈希冲突 |
如何计算索引? | index = hash & (table.length - 1) |
为什么用 & (length - 1) 而不是 % length ? | 位运算比取模运算更快 |
如果 length 不是 2 的幂,会怎样? | 索引分布不均,哈希冲突增加,性能下降 |
HashMap 如何保证 length 是 2 的幂? | tableSizeFor(cap) 方法,自动调整 capacity |
问题7: 比较 HashSet、LinkedHashSet 和 TreeSet 三者的异同
1. 共同点(相同点)
✅ 1.1. 都实现了 Set
接口
-
不允许存储重复元素,如果尝试添加相同元素,新的数据会被忽略。
✅ 1.2. 允许 null
值
-
HashSet
和LinkedHashSet
允许存储一个null
值。 -
TreeSet
不允许null
(因为TreeSet
依赖compareTo()
进行排序,null
无法比较)。
✅ 1.3. 不是线程安全的
-
三者都不是线程安全的,若在多线程环境下使用,需要使用
Collections.synchronizedSet()
或ConcurrentSkipListSet
。
Set<Integer> set = Collections.synchronizedSet(new HashSet<>());
✅ 1.4. 不能通过索引访问元素
-
Set
没有get(int index)
方法,不能像List
那样通过索引访问。
2. 不同点(区别)
对比项 | HashSet | LinkedHashSet | TreeSet |
---|---|---|---|
底层数据结构 | 哈希表( | 哈希表 + 双向链表 | 红黑树( |
元素存储顺序 | 无序 | 按照插入顺序 | 自动排序(默认升序,可自定义) |
是否排序 | ❌ 不排序 | ❌ 不排序(但保持插入顺序) | ✅ 排序(按 |
允许 | ✅ 允许 1 个 | ✅ 允许 1 个 | ❌ 不允许 |
查询性能 | 🚀 O(1)(哈希存储,最快) | 🚀 O(1)(哈希存储) | 🐌 O(log n)(红黑树,较慢) |
插入性能 | 🚀 O(1)(哈希存储) | 🚀 O(1)(哈希存储) | 🐌 O(log n)(红黑树,较慢) |
删除性能 | 🚀 O(1) | 🚀 O(1) | 🐌 O(log n) |
适用场景 | 需要快速查找的集合 | 需要保留插入顺序的集合 | 需要排序的集合 |
3. 三者的底层实现
(1)HashSet
-
底层使用
HashMap
,Set
的值存储在HashMap
的 key 中,所有 value 设为Object
类型的固定值。 -
元素无序存储,无法保证插入顺序。
public HashSet() {map = new HashMap<>();
}
public boolean add(E e) {return map.put(e, PRESENT) == null;
}
✅ 适用场景:需要快速去重,不关心元素顺序。
(2)LinkedHashSet
-
底层使用
LinkedHashMap
,通过双向链表维护插入顺序。 -
保证遍历顺序与插入顺序一致。
public LinkedHashSet() {super(16, .75f, true); // 继承 LinkedHashMap }
✅ 适用场景:去重但保持插入顺序,如 LRU 缓存。
(3)TreeSet
-
底层使用
TreeMap
,基于红黑树存储。 -
按
compareTo()
结果自动排序,默认升序。 -
不允许
null
,因为null
无法参与比较。public TreeSet() {this(new TreeMap<>()); }
✅ 适用场景:需要排序的集合,如排行榜、时间排序的日志。
4. 示例代码
(1)HashSet
Set<String> hashSet = new HashSet<>();
hashSet.add("B");
hashSet.add("A");
hashSet.add("C");
System.out.println(hashSet); // 输出无序:[A, C, B](顺序可能不同)
(2)LinkedHashSet
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("B");
linkedHashSet.add("A");
linkedHashSet.add("C");
System.out.println(linkedHashSet); // 输出顺序:["B", "A", "C"]
(3)TreeSet
Set<String> treeSet = new TreeSet<>();
treeSet.add("B");
treeSet.add("A");
treeSet.add("C");
System.out.println(treeSet); // 输出顺序:["A", "B", "C"](自动排序)
5. 选择建议(如何选择?)
场景 | 推荐使用 |
---|---|
需要去重,不关心顺序,查询性能最高 |
|
需要去重,且保持插入顺序 |
|
需要去重,且按大小排序 |
|
6. 重点面试问题
💡 1. HashSet、LinkedHashSet、TreeSet 的区别?
-
HashSet
:基于HashMap
,无序存储,O(1) 查询 -
LinkedHashSet
:基于LinkedHashMap
,有序存储 -
TreeSet
:基于TreeMap
,自动排序,O(log n) 查询
💡 2. HashSet 为什么查询快?
-
HashSet
基于HashMap
,使用hashCode()
计算索引,查询平均 O(1)。
💡 3. TreeSet 为什么不允许 null
?
-
TreeSet
依赖compareTo()
进行排序,而null
不能比较。
💡 4. TreeSet 是如何排序的?
-
通过
Comparable
接口 (compareTo()
) 或Comparator
自定义排序规则。
7. 总结
Set 类型 | 底层数据结构 | 顺序 | 查询性能 | 插入/删除性能 | 是否排序 |
---|---|---|---|---|---|
| 哈希表( | 无序 | 🚀 | 🚀 | ❌ |
| 哈希表 + 双向链表( | 插入顺序 | 🚀 | 🚀 | ❌ |
| 红黑树( | 排序 | 🐌 | 🐌 | ✅ |
🔹 总结
-
HashSet
最快,但无序。 -
LinkedHashSet
有序,比HashSet
稍慢。 -
TreeSet
自动排序,但性能最慢(O(log n)
)。
问题8: HashMap 和 Hashtable 的区别?HashMap 和 HashSet 区别?HashMap 和 TreeMap 区别? ConcurrentHashMap 和 Hashtable 的区别?
1. HashMap vs. Hashtable(区别)
对比项 | HashMap | Hashtable |
---|---|---|
线程安全 | ❌ 非线程安全 | ✅ 线程安全(方法使用 |
底层数据结构 | 数组 + 链表(JDK 1.7) | 数组 + 链表 |
性能 | 🚀 高(无锁) | 🐌 低(全表锁) |
| ✅ 允许 | ❌ 不允许 |
扩容方式 |
|
|
适用场景 | 单线程或高并发(推荐使用 | 低并发场景(通常不推荐) |
✅ 总结:
-
HashMap
性能更高,适用于 单线程或并发环境(推荐ConcurrentHashMap
)。 -
Hashtable
所有方法加锁,线程安全但性能低,一般不推荐使用。
💡 面试问法:
-
为什么
Hashtable
不推荐使用?-
Hashtable
全表锁,并发效率低,通常使用ConcurrentHashMap
替代。
-
2. HashMap vs. HashSet(区别)
对比项 | HashMap | HashSet |
---|---|---|
实现接口 |
|
|
数据存储方式 |
| 仅存 |
底层数据结构 | 数组 + 链表 + 红黑树(JDK 1.8) | 基于 |
是否允许重复 | ❌ | ❌ 不允许重复元素 |
| ✅ | ✅ 允许一个 |
查询性能 | 🚀 | 🚀 |
适用场景 | 需要存储键值对(如用户 ID -> 用户信息) | 需要存储唯一元素集合(如用户 ID 集合) |
✅ 总结:
-
HashMap
存储键值对,HashSet
仅存储 key(基于HashMap
实现)。 -
HashSet
去重能力基于HashMap
的key
唯一性。
💡 面试问法:
-
HashSet 为什么是基于 HashMap 实现的?
-
HashSet
本质上就是HashMap
,value
始终存一个固定对象。
-
public boolean add(E e) {return map.put(e, PRESENT) == null;
}
3. HashMap vs. TreeMap(区别)
对比项 | HashMap | TreeMap |
---|---|---|
底层数据结构 | 数组 + 链表 + 红黑树 | 红黑树( |
排序方式 | ❌ 无序 | ✅ 自动排序(默认升序) |
查询性能 | 🚀 | 🐌 |
| ✅ | ❌ |
适用场景 | 需要快速查找的键值对 | 需要排序的键值对(如排行榜) |
✅ 总结:
-
HashMap
查询快(O(1)),无序,适合快速存取数据。 -
TreeMap
自动排序(O(log n)),适用于需要排序的场景。
💡 面试问法:
-
如果 HashMap 需要排序怎么办?
-
使用
TreeMap
或者LinkedHashMap
。
-
4. ConcurrentHashMap vs. Hashtable(区别)
对比项 | ConcurrentHashMap(推荐) | Hashtable(已淘汰) |
---|---|---|
底层数据结构 | JDK 1.7:分段锁( | 哈希表 + 全表锁 |
锁的粒度 | 局部锁(高并发) | 全表锁(低效) |
查询性能 | 🚀 高(读 | 🐌 低(所有方法 |
| ❌ 不允许 | ❌ 不允许 |
适用场景 | 高并发环境(推荐) | 低并发环境(已淘汰) |
✅ 总结:
-
ConcurrentHashMap
替代Hashtable
,采用 CAS + 局部锁,更高效。 -
Hashtable
线程安全但性能低,一般不再使用。
💡 面试问法:
-
ConcurrentHashMap 如何保证线程安全?
-
JDK 1.7:
Segment
分段锁(16 个小锁,提高并发性能)。 -
JDK 1.8:
CAS +
synchronized` + 红黑树(更高效)。
-
5. 总结
对比项 | HashMap vs. Hashtable | HashMap vs. HashSet | HashMap vs. TreeMap | ConcurrentHashMap vs. Hashtable |
---|---|---|---|---|
线程安全 | ❌ HashMap 非线程安全,❌ Hashtable 线程安全(但慢) | HashMap 存 key-value,HashSet 只存 key | HashMap 无序,TreeMap 排序 | ConcurrentHashMap 更高效(局部锁),Hashtable 全表锁(淘汰) |
底层结构 | HashMap:哈希表,Hashtable:哈希表(带锁) | HashSet 基于 HashMap | HashMap 哈希表,TreeMap 红黑树 | ConcurrentHashMap 分段锁(JDK 1.7) / CAS + |
查询性能 | 🚀 | 🚀 | 🚀 | 🚀 |
是否排序 | ❌ 都无序 | ❌ 都无序 | ❌ HashMap 无序,✅ TreeMap 排序 | ❌ 都无序 |
null 允许性 | HashMap 允许 | ✅ HashSet 允许一个 | ❌ TreeMap 不允许 | ❌ ConcurrentHashMap 不允许 |
🚀 面试重点:
-
高并发环境:
ConcurrentHashMap
代替Hashtable
-
需要排序:用
TreeMap
-
去重:用
HashSet
(基于HashMap
)
问题9:ConcurrentHashMap 线程安全的具体实现方式/底层具体实现
1. JDK 1.7 中的实现(分段锁)
在 JDK 1.7 中,ConcurrentHashMap
采用了 分段锁(Segment Locking) 机制。HashMap
的每个**桶(Segment)**都是独立加锁的,允许多个线程并发操作不同的段,但同一段内的操作需要加锁。
1.1. 分段锁模型
-
ConcurrentHashMap
会把整个 map 划分为多个 段(Segment),每个段内部维护一个HashMap
,并且每个段有一个锁。 -
每个段都可以并发操作,不同段之间的锁是独立的,这就避免了全表锁定,从而大大提高了并发性能。
-
每个段的默认大小是 16(即 16 个桶),并且段数可以动态调整。
1.2. 锁粒度
-
锁定段: 每次只能锁住某个段(而不是整个
ConcurrentHashMap
)。 -
不同段的并发访问: 不同线程可以并发地对不同段进行
get
、put
等操作。 -
同一段的并发访问: 如果多个线程访问同一段的不同元素,则这些线程会被阻塞,直到持有该段锁的线程释放锁。
1.3. 分段锁的代码示例
public class ConcurrentHashMap<K, V> {static final int MAX_SEGMENT = 16; // 默认分成16个段final Segment<K, V>[] segments;// Segment 内部结构static final class Segment<K, V> extends ReentrantLock implements Serializable {transient volatile HashEntry<K, V>[] table;transient volatile int count;final float loadFactor;Segment(int initialCapacity, float loadFactor) {this.loadFactor = loadFactor;// Initialize the segment's hash table with the given capacity}// Segment 内部的操作方法(put、get 等)public V get(Object key) {// 获取 key 对应的 value}public void put(K key, V value) {// 将 key-value 对存入当前段的 table 中}}// 主 Map 操作,基于多个 Segment 来完成public V get(Object key) {int hash = key.hashCode();Segment<K, V> segment = segmentFor(hash);return segment.get(key);}public void put(K key, V value) {int hash = key.hashCode();Segment<K, V> segment = segmentFor(hash);segment.put(key, value);}private Segment<K, V> segmentFor(int hash) {return segments[(hash >>> 16) & (segments.length - 1)];}
}
1.4. 分段锁模型的优缺点
优点:
-
细粒度锁:只锁住某一段,多个线程可以并行操作不同的段,并行度较高。
-
线程安全:每个段是独立加锁的,避免了全表加锁带来的性能瓶颈。
缺点:
-
内存占用高:每个段内部都是一个完整的
HashMap
,在大数据量时可能会占用较多内存。 -
锁竞争:如果多个线程访问同一段,仍然会发生阻塞。
2. JDK 1.8 中的实现(CAS + synchronized
)
在 JDK 1.8 中,ConcurrentHashMap
做了重大优化,去除了分段锁,改用 CAS(Compare-And-Swap) 和 synchronized
来实现线程安全。其底层不再使用多个段,而是直接将数据存储在一个 桶数组(数组 + 链表 + 红黑树) 中,通过 分段锁和局部锁 的方式来提供高并发性。
2.1. CAS 和 synchronized
的组合
-
CAS:
ConcurrentHashMap
采用 乐观锁,在更新数据时首先通过 CAS 操作尝试更新,如果失败(例如并发更新),则进行重试。 -
synchronized
:对于需要一致性的操作(如扩容),采用synchronized
锁定局部区域(如某个桶),保证数据一致性。
2.2. JDK 1.8 的底层结构
-
桶数组:和
HashMap
类似,ConcurrentHashMap
内部使用数组来存储键值对。每个桶可以是 链表(哈希冲突时)或 红黑树(当链表过长时转换为红黑树)。 -
每个桶的操作:
-
使用
synchronized
锁定某个桶的插入、删除、查找操作。 -
对于普通操作(如插入、查询),采用 CAS 来保证数据的一致性。
-
-
扩容:当负载因子超过一定阈值时,
ConcurrentHashMap
会进行扩容(桶数翻倍),这时需要用synchronized
来保护扩容过程。
2.3. JDK 1.8 代码示例
public class ConcurrentHashMap<K, V> {// 存储数据的桶数组transient volatile Node<K, V>[] table;private final float loadFactor = 0.75f;private transient volatile int sizeCtl;// 锁定某个桶public V get(Object key) {int hash = hash(key);Node<K, V> e;if ((e = table[hash & (table.length - 1)]) != null) {// 通过链表查找,若链表长度大于阈值转为红黑树synchronized (e) {// 查找并返回}}return null;}// 使用 CAS 更新值public boolean putIfAbsent(K key, V value) {int hash = hash(key);Node<K, V> e;if ((e = table[hash & (table.length - 1)]) != null) {// 使用 CAS 更新,如果失败则重试}return true;}// 扩容private void resize() {synchronized (this) {// 扩容并重置桶数组}}// hash 计算(保证一致性)private int hash(Object key) {return key.hashCode();}
}
2.4. JDK 1.8 实现的优缺点
优点:
-
更高效:通过 CAS 和局部锁提高了并发性能,不需要全表加锁。
-
扩展性强:自动扩容时,通过同步锁保护内存,避免了扩容过程中的数据不一致。
-
支持高并发:多个线程可以在不同桶上并行操作,极大提高了吞吐量。
缺点:
-
复杂度高:相比 JDK 1.7,JDK 1.8 的实现更加复杂,使用了 CAS 和
synchronized
等技术,需要处理各种竞争条件。 -
内存开销:每个桶存储的是
Node
,这比HashMap
更占用内存。
3. 线程安全的关键点
-
CAS(Compare-And-Swap):乐观锁的一种实现方式,通过原子操作检查和更新数据。如果当前数据符合预期,则修改数据;否则重试。
-
synchronized
:用于保护需要保证一致性的关键操作(如扩容、迁移)。 -
红黑树优化:当链表的长度超过一定阈值(8),
HashMap
会将链表转换为红黑树,优化查询性能。
4. 总结
-
JDK 1.7:使用 分段锁,将
ConcurrentHashMap
划分为多个段,每个段有一个独立的锁,多个线程可以并行操作不同的段。 -
JDK 1.8:采用 CAS +
synchronized
技术,使用桶数组(数组 + 链表 + 红黑树)来存储数据。通过局部锁和 CAS 实现高并发。 -
线程安全性:
ConcurrentHashMap
通过 细粒度锁(桶级别、段级别锁)和 CAS 操作 保证线程安全。
ConcurrentHashMap
是高并发场景下非常常用的工具,在需要线程安全的并发环境下,它是 Hashtable
和 synchronizedMap
的更好替代品。
相关文章:
Java集合面试总结(题目来源JavaGuide)
问题1:说说 List,Set,Map 三者的区别? 在 Java 中,List、Set 和 Map 是最常用的集合框架(Collection Framework)接口,它们的主要区别如下: 1. List(列表) 特点…...
【区块链】深入理解椭圆曲线密码学(ECC)
🌈个人主页: 鑫宝Code 🔥热门专栏: 闲话杂谈| 炫酷HTML | JavaScript基础 💫个人格言: "如无必要,勿增实体" 文章目录 深入理解椭圆曲线密码学(ECC)1. 概述2. 椭圆曲线的数学基础2.1 基本定义2.2 有限…...
接口游标分页
由于数据库本身的的限制(以MySQL为例),以 page_num,page_size 为代表的偏移分页方式不可避免的会遇到深分页问题。 不过用户若要找符合条件的少量数据,通过翻页则十分低效,且大多用户也不会往后翻很多页,故对于C端筛选数据场景,限制分页数量增加筛选条件才是标准解决方…...
大数据数仓实战项目(离线数仓+实时数仓)2
目录 1.课程目标和课程内容介绍 2.数仓维度建模设计 3.数仓为什么要分层 4.数仓分层思想和作用 5.数仓中表的种类和同步策略 6.数仓中表字段介绍以及表关系梳理 订单表itcast_orders 订单明细表 itcast_order_goods 商品信息表 itcast_goods 店铺表 itcast_shops 商…...
C++输入输出(上)
cin和cout cin是C中提供的标准输入流对象,一般针对的是键盘,也就是从键盘上输入的字符流,使用 cin来进行数据的提取,cin一般是和 >> (流提取运算符) 配合使用的。 cin的功能和scanf是类似的 cout是C中提供的标准输出流对象,一般针对的是控制台的窗口,也就是将数据以字符…...
SpringBoot 连接Elasticsearch带账号密码认证 ES连接 加密连接
依赖 <dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId> </dependency>配置文件 es:ip: 172.23.4.130port: 9200user: elasticpassword: qwertyuiop读取配置文件…...
选择排序
选择排序的基本思想: 每⼀次从待排序的数据元素中选出最⼩(或最⼤)的⼀个元素,存放在序列的起始位置,直到全部待 排序的数据元素排完。 直接选择排序 1. 在元素集合 array[i]--array[n-1] 中选择关键码最⼤(⼩)的数据…...
Linux——进程概念
目录 一、系统调用和库函数概念二、基本概念三、描述进程-PCB3.1 task_struct-PCB的一种3.2 task_ struct内容分类 四、组织进程五、查看进程六、通过系统调用获取进程标示符七、通过系统调用创建进程- fork初始7.1 fork函数创建子进程7.2 fork 之后通常要用 if 进行分流 八、进…...
强化学习笔记(5)——PPO
PPO视频课程来源 首先理解采样期望的转换 变量x在p(x)分布下,函数f(x)的期望 等于f(x)乘以对应出现概率p(x)的累加 经过转换后变成 x在q(x)分布下,f(x)*p(x)/q(x) 的期望。 起因是:求最大化回报的期望,所以对ceta求梯度 具体举例…...
Java设计模式:行为型模式→状态模式
Java 状态模式详解 1. 定义 状态模式(State Pattern)是一种行为型设计模式,它允许对象在内部状态改变时改变其行为。状态模式通过将状态需要的行为封装在不同的状态类中,实现对象行为的动态改变。该模式的核心思想是分离不同状态…...
postgresql的用户、数据库和表
在 PostgreSQL 中,用户、数据库和表是关系型数据库系统的基本组成部分。理解这些概念对数据库管理和操作至关重要。下面是对这些概念的详细解释: 1. 用户(User) 在 PostgreSQL 中,用户(也称为 角色&#…...
什么是Rust?它有什么特点?为什么要学习Rust?
什么是Rust?它有什么特点?为什么要学习Rust? 如果你是一名编程初学者,或者已经有一些编程经验但对Rust感兴趣,那么这篇文章就是为你准备的!我们将用简单易懂的语言,带你了解Rust是什么、它有什…...
Maven(Ⅱ):依赖范围,依赖传递,依赖阻断,可选依赖
1. Maven 依赖范围 概念 依赖范围(Dependency Scope)用于控制依赖在不同构建阶段的可见性和可用性。Maven 定义了几种不同的依赖范围,每种范围都有其特定的使用场景。 常见依赖范围及用途 compile:默认的依赖范围,…...
个人c项目 java项目解释
1. 测试环境与方法 中文: 本地测试环境:可以在一台配置中等的电脑上构建一个测试环境,利用现成的大词库数据(例如英文词典或自定义数据集)来构建 Trie。使用 C 语言的编译器(例如 gcc)编译项目&…...
51单片机看门狗系统
在 STC89C52 单片机中,看门狗控制寄存器的固定地址为 0xE1。此地址由芯片厂商在硬件设计时确定,但是它在头文件中并未给出,因此在使用看门狗系统时需要声明下这个特殊功能寄存器 sfr WDT_CONTR 0xE1; 本案将用一个小灯的工作状况来展示看门…...
爬虫基础(五)爬虫基本原理
目录 一、爬虫是什么 二、爬虫过程 (1)获取网页 (2)提取信息 (3)保存数据 三、爬虫可爬的数据 四、爬虫问题 一、爬虫是什么 互联网,后面有个网字,我们可以把它看成一张蜘蛛网…...
Android 使用ExpandableListView时,需要注意哪些细节
1. 布局属性设置 尺寸属性 宽度和高度:要合理设置 android:layout_width 和 android:layout_height 属性。如果设置为 match_parent,它会填满父容器;设置为 wrap_content,则会根据内容自动调整大小。例如,若想让 Exp…...
人工智能赋能企业系统架构设计:以ERP与CRM系统为例
一、引言 1.1 研究背景与意义 在数字化时代,信息技术飞速发展,人工智能(Artificial Intelligence, AI)作为一项具有变革性的技术,正深刻地影响着各个领域。近年来,AI 在技术上取得了显著突破,…...
使用HttpClient和HttpRequest发送HTTP请求
项目中经常会用到向第三方系统发送请求来传递数据或者获得信息,一般用的比较多的为HttpClient 和 HttpRequest,这里简要总结一下 HttpClient 和 HttpRequest 的用法 一、HttpClient 1. 发送get请求 public static String get(String url, Map<Stri…...
深度解析:网站快速收录与服务器性能的关系
本文转自:百万收录网 原文链接:https://www.baiwanshoulu.com/37.html 网站快速收录与服务器性能之间存在着密切的关系。服务器作为网站运行的基础设施,其性能直接影响到搜索引擎对网站的抓取效率和收录速度。以下是对这一关系的深度解析&am…...
Android记事本App设计开发项目实战教程2025最新版Android Studio
平时上课录了个视频,从新建工程到打包Apk,从头做到尾,没有遗漏任何实现细节,欢迎学过Android基础的同学参加,如果你做过其他终端软件开发,也可以学习,快速上手Android基础开发。 Android记事本课…...
DeepSeek-R1大模型学习笔记
DeepSeek-R1模型架构设计 DeepSeek-R1基于DeepSeek-V3 base模型,提出了一系列训练策略,包括基于纯强化学习的训练(DeepSeek-R1-Zero)、基于多阶段的训练和冷启动(DeepSeek-R1)、知识蒸馏等。下面的思维导图…...
Unity游戏(Assault空对地打击)开发(4) 碰撞体和刚体的添加
前言 飞机和世界的大小关系不太对,我稍微缩小了一下飞机。 详细步骤 选中所有地形对象,如果没有圈起的部分,点击Add Component搜索添加。 接着选中Player对象,添加这两个组件,最好(仅对于本项目开发&#x…...
每日一题——滑动窗口的最大值
滑动窗口的最大值 题目描述示例说明 解题思路双端队列的特点实现步骤代码实现(C语言)代码解析 总结 题目描述 给定一个长度为 n 的数组 num 和滑动窗口的大小 size,找出所有滑动窗口里数值的最大值。 例如,如果输入数组 {2, 3, …...
DeepSeek 的含金量还在上升
大家好啊,我是董董灿。 最近 DeepSeek 越来越火了。 网上有很多针对 DeepSeek 的推理测评,除此之外,也有很多人从技术的角度来探讨 DeepSeek 带给行业的影响。 比如今天就看到了一篇文章,探讨 DeepSeek 在使用 GPU 进行模型训练…...
list容器(详解)
list的介绍及使用(了解,后边细讲) 1.1 list的介绍(双向循环链表) https://cplusplus.com/reference/list/list/?kwlist(list文档介绍) 1. list是可以在常数范围内在任意位置进行插入和删除的序…...
FinRobot:一个使用大型语言模型的金融应用开源AI代理平台
“FinRobot: An Open-Source AI Agent Platform for Financial Applications using Large Language Models” 论文地址:https://arxiv.org/pdf/2405.14767 Github地址:https://github.com/AI4Finance-Foundation/FinRobot 摘要 在金融领域与AI社区间&a…...
【llm对话系统】大模型 Llama 源码分析之 LoRA 微调
1. 引言 微调 (Fine-tuning) 是将预训练大模型 (LLM) 应用于下游任务的常用方法。然而,直接微调大模型的所有参数通常需要大量的计算资源和内存。LoRA (Low-Rank Adaptation) 是一种高效的微调方法,它通过引入少量可训练参数,固定预训练模型的权重,从而在保持性能的同时大…...
为AI聊天工具添加一个知识系统 之86 详细设计之27 数据处理:ETL
本文要点 ETL 数据提取 作为 数据项目的起点。数据的整个三部曲--里程碑式的发展进程: ETL : 1分形 Type()-层次Broker / 2完形 Method() - 维度Delegate /3 整形 Class() - 容器 Agent 1变象。变象 脸谱Extractor - 缠度(物理 皮肤缠度…...
「全网最细 + 实战源码案例」设计模式——策略模式
核心思想 策略模式(Strategy Pattern)是一种行为型设计模式,用于定义一系列算法或策略,将它们封装成独立的类,并使它们可以相互替换,而不影响客户端的代码,提高代码的可维护性和扩展性。 结构 …...
框架与代码的形状
作为一个代码的设计者,我之前讨论过代码的形状,从“名字”出发,进行讨论。代码的形状:重构的方向-CSDN博客 从比喻的角度来看,名字似代码的血和肉,而框架则似代码的骨架。 猎豹和大象 在大自然中&…...
解决vscode扩展插件开发webview中的请求跨域问题
在webview中是无法发送跨域请求的,可以通过消息机制,在插件中发请求,然后将请求结果传递给webview 我的代码是基于vscode-webview-ui-toolkit-samples-vue来写的 webview vue组件中的代码示例 async function initData() {// 向插件发送消…...
junit5定制点
一、JUnit 5 自定义定制点是什么? JUnit 5 提供了强大的扩展模型(Extension Model),允许开发者通过实现特定接口(如 BeforeEachCallback、ParameterResolver)自定义测试行为。这些接口称为定制点ÿ…...
基于SpringBoot的信息技术知识赛系统的设计与实现(源码+SQL脚本+LW+部署讲解等)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...
【Rust自学】20.1. 最后的项目:单线程Web服务器
喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(・ω・) 20.1.1. 什么是TCP和HTTP Web 服务器涉及的两个主要协议是超文本传输协议(Hypertext T…...
LabVIEW涡轮诊断系统
一、项目背景与行业痛点 涡轮机械是发电厂、航空发动机、石油化工等领域的核心动力设备,其运行状态直接关系到生产安全与经济效益。据统计,涡轮故障导致的非计划停机可造成每小时数十万元的经济损失,且突发故障可能引发严重安全事故。传统人…...
想品客老师的第十天:类
类是一个优化js面向对象的工具 类的声明 //1、class User{}console.log(typeof User)//function//2、let Hdclass{}//其实跟1差不多class Stu{show(){}//注意这里不用加逗号,对象才加逗号get(){console.log(后盾人)}}let hdnew Stu()hd.get()//后盾人 类的原理 类…...
注解(Annotation)
注解(Annotation)在 Java 中可以用来简化类的使用,使得被注解的类能够被自动发现、自动创建并在需要的地方直接调用,而不需要手动创建实例。具体来说,注解是用来标识类、方法、字段等的,它们通常与一些框架…...
使用开源项目:pdf2docx,让PDF转换为Word
目录 1.安装python 2.安装 pdf2docx 3.使用 pdf2docx 转换 PDF 到 Word pdf2docx:GitCode - 全球开发者的开源社区,开源代码托管平台 环境:windows电脑 1.安装python Download Python | Python.org 最好下载3.8以上的版本 安装时记得选择上&#…...
编程AI深度实战:AI编程工具哪个好? Copilot vs Cursor vs Cody vs Supermaven vs Aider
Cursor自己可以看成一个IDE,而且有强大的RAG功能,这让它对你的意图感知非常厉害,可以精确补全,可以感受代码片段 Aider可以看作一个袖珍,灵活,强大的扳手,怎么用都行,可以放在脚本里调用,可以看代码,可以修改代码。相比Cursor而言,它感受的是文件级别,颗粒度有些不…...
如何安全地管理Spring Boot项目中的敏感配置信息
在开发Spring Boot应用时,我们经常需要处理一些敏感的配置信息,比如数据库密码、API密钥等。以下是一个最佳实践方案: 1. 创建配置文件 application.yml(版本控制) spring:datasource:url: ${MYSQL_URL:jdbc:mysql…...
为AI聊天工具添加一个知识系统 之77 详细设计之18 正则表达式 之5
本文要点 昨天讨论了 本项目(AI聊天工具添加一个知识系统)中正则表达式模板的设计中可能要考虑到的一些问题(讨论到的内容比较随意,暂时无法确定 那些考虑 是否 应该是正则表达式模板设计要考虑的以及 是否完整)。今天…...
Ubuntu下Tkinter绑定数字小键盘上的回车键(PySide6类似)
设计了一个tkinter程序,在Win下绑定回车键,直接绑定"<Return>"就可以使用主键盘和小键盘的回车键直接“提交”,到了ubuntu下就不行了。经过搜索,发现ubuntu下主键盘和数字小键盘的回车键,名称不一样。…...
安全实验作业
一 拓扑图 二 要求 1、R4为ISP,其上只能配置IP地址;R4与其他所有直连设备间均使用共有IP 2、R3-R5-R6-R7为MGRE环境,R3为中心站点; 3、整个OSPF环境IP基于172.16.0.0/16划分; 4、所有设备均可访问R4的环回&#x…...
NOTEPAD++编写abap
参考下面三个链接 Notepad ABAP代码高亮显示_notepad代码高亮颜色-CSDN博客 百度安全验证 ABAP Syntax Highlighting in Notepad Part 2 - SAP Community 最后XML文件看看你可以自己增加些新语法的高亮显示...
基于python的体育新闻数据可视化及分析
项目 :北京冬奥会体育新闻数据可视化及分析 摘 要 随着社会的不断进步与发展,新时代下的网络媒体获取的信息也更加庞大和繁杂,相比于传统信息来源更加难以分析和辨别,造成了新时代媒体从业者撰写新闻的难度。在此背景下ÿ…...
C# 精炼题18道题(类,三木运算,Switch,计算器)
1.数组元素和 2.数组元素乘积 3.数组元素平均数 4.数组中最大值 5.数组中的偶数 6.数组中的阶乘 7.数组反转 8.字符串反转 9.回文字符串 10.检查回文 11.最小最大值 12.找素数 13.字符串中的最长无重复字符串 14.字符串去重 15.数组中计算两数之和 16.数字到字符…...
vue2语法速通
首先,git clone下来的项目要npm install下载依赖,如果是vue项目,运行通常npm run serve或者npm run dev vue速通一下 使用vite创建项目(较快) npm create vite 配置文件 src/ ├── assets/ # 存放…...
LabVIEW图片识别逆向建模系统
本文介绍了一个基于LabVIEW的图片识别逆向建模系统的开发过程。系统利用LabVIEW的强大视觉处理功能,通过二维图片快速生成对应的三维模型,不仅降低了逆向建模的技术门槛,还大幅提升了建模效率。 项目背景 在传统的逆向建模过程中…...
idea隐藏无关文件
idea隐藏无关文件 如果你想隐藏某些特定类型的文件(例如 .log 文件或 .tmp 文件),可以通过以下步骤设置: 打开设置 在菜单栏中选择 File > Settings(Windows/Linux)或 IntelliJ IDEA > Preference…...