目录
✅ HashSet:基于 HashMap 实现的无序不可重复集合
✅ ConcurrentHashMap:线程安全的 HashMap 替代品
✅ ConcurrentModificationException 是怎么产生的?
一、引言
在 Java 开发中,集合类是我们最常用的工具之一。但很多同学只停留在“会用”的层面,而没有真正理解其底层原理和潜在陷阱。
本文将带你:
- 理解
ArrayList的扩容机制;- 掌握
HashMap在 JDK7 和 JDK8 的差异;- 学会避免
ConcurrentModificationException;- 了解
HashSet如何实现去重;- 明确线程安全集合类的选择;
- 结合实际代码示例,帮助你在面试中游刃有余。
二、List 接口详解
✅ ArrayList:动态数组实现
📌 示例1:基本使用
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
System.out.println(list); // 输出 [A, B]
📌 底层结构:
- 内部维护一个
Object[]数组; - 默认初始容量为 10;
- 超出容量时自动扩容(增长原容量的 50%);
📌 示例2:扩容测试
ArrayList<Integer> list = new ArrayList<>(5);
for (int i = 0; i < 20; i++) {
list.add(i);
System.out.println("Size: " + list.size() + ", Capacity: " + getCapacity(list));
}
📌 说明:
- 可通过反射获取
ArrayList的elementData数组长度来查看当前容量; - 扩容是一个性能开销较大的操作,频繁添加建议指定初始容量。
📌 常见误区:
❌ 不要在循环中频繁添加元素而不指定初始容量;
✅ 合理预估大小可以提高性能。
✅ LinkedList:链表实现
📌 示例3:插入效率对比
List<Integer> arrayList = new ArrayList<>();
List<Integer> linkedList = new LinkedList<>();
// 插入到中间位置
for (int i = 0; i < 10000; i++) {
arrayList.add(i);
linkedList.add(i);
}
long start = System.currentTimeMillis();
arrayList.add(5000, 999);
System.out.println("ArrayList 插入耗时:" + (System.currentTimeMillis() - start));
start = System.currentTimeMillis();
linkedList.add(5000, 999);
System.out.println("LinkedList 插入耗时:" + (System.currentTimeMillis() - start));
📌 输出:
ArrayList 插入耗时:3ms
LinkedList 插入耗时:1ms
📌 底层结构:
- 使用双向链表;
- 插入删除快(O(1));
- 随机访问慢(O(n));
📌 适用场景:
- 频繁插入/删除;
- 不需要随机访问;
三、Set 接口详解
✅ HashSet:基于 HashMap 实现的无序不可重复集合
📌 示例4:基本使用
Set<String> set = new HashSet<>();
set.add("apple");
set.add("banana");
set.add("apple"); // 重复元素不会被添加
System.out.println(set); // 输出 [banana, apple]
📌 去重机制:
- 依赖
hashCode()和equals()方法; - 如果两个对象的
hashCode()相同,再调用equals()比较; - 若返回 true,则视为重复;
📌 示例5:自定义类的去重
class Person {
String name;
public Person(String name) {
this.name = name;
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Person)) return false;
return this.name.equals(((Person) obj).name);
}
}
Set<Person> people = new HashSet<>();
people.add(new Person("Tom"));
people.add(new Person("Tom")); // 会被认为是重复元素
📌 开发建议:
- 自定义类放入
HashSet前必须重写hashCode()和equals(); - 否则默认使用
Object类的方法,导致无法正确去重。
✅ TreeSet:有序 Set,支持自然排序或定制排序
📌 示例6:自然排序
Set<Integer> set = new TreeSet<>();
set.add(3);
set.add(1);
set.add(2);
System.out.println(set); // 输出 [1, 2, 3]
📌 示例7:定制排序
Set<String> set = new TreeSet<>((o1, o2) -> o2.compareTo(o1));
set.add("a");
set.add("b");
set.add("c");
System.out.println(set); // 输出 [c, b, a]
📌 注意:
TreeSet不允许插入 null;- 否则抛出
NullPointerException(因为要比较);
四、Map 接口详解
✅ HashMap:哈希表实现,非线程安全
📌 示例8:基本使用
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
System.out.println(map.get("A")); // 输出 1
📌 JDK8 的优化:
- 引入红黑树解决哈希冲突;
- 当链表长度 ≥ 8 且桶数 ≥ 64 时,链表转为红黑树;
- 查询效率从 O(n) 提升到 O(log n);
📌 示例9:哈希冲突演示
class BadKey {
@Override
public int hashCode() {
return 1; // 所有对象 hash 相同
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
}
Map<BadKey, String> map = new HashMap<>();
for (int i = 0; i < 1000; i++) {
map.put(new BadKey(), "value" + i);
}
📌 分析:
- 所有 key 都落在同一个桶里;
- 形成一条长链表,查询效率下降;
- JDK8 会自动将其转为红黑树优化查找效率;
✅ ConcurrentHashMap:线程安全的 HashMap 替代品
📌 示例10:多线程 put
Map<Integer, Integer> map = new ConcurrentHashMap<>();
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
final int key = i;
executor.submit(() -> map.put(key, key * 2));
}
executor.shutdown();
System.out.println("Size: " + map.size()); // 输出 1000
📌 特点:
- 使用分段锁(JDK7)或 CAS + synchronized(JDK8);
- 支持高并发读写;
- 多线程下推荐使用;
五、迭代器与并发修改异常
✅ ConcurrentModificationException 是怎么产生的?
📌 示例11:错误的遍历删除方式
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
for (String s : list) {
if (s.equals("A")) {
list.remove(s); // 抛出 ConcurrentModificationException
}
}
📌 原因:
- 使用增强型 for 循环时,内部使用了迭代器;
- 修改集合结构(非通过迭代器)会触发 fail-fast 机制;
✅ 正确做法:使用迭代器删除
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("A")) {
it.remove(); // 安全删除
}
}
📌 总结:
- 不要使用普通 for 或增强型 for 删除元素;
- 应使用迭代器的
remove(); - 或者使用并发集合类(如
CopyOnWriteArrayList);
六、总结
| 核心知识点 | 内容 |
|---|---|
| ArrayList | 动态数组,适合随机访问 |
| LinkedList | 链表结构,适合插入删除 |
| HashSet | 基于 HashMap,通过 hashCode 和 equals 去重 |
| TreeSet | 红黑树实现,按键排序 |
| HashMap | 哈希表 + 链表/红黑树,非线程安全 |
| ConcurrentHashMap | 线程安全的 Map 实现 |
| ConcurrentModificationException | 遍历时不能直接修改集合结构 |
1475

被折叠的 条评论
为什么被折叠?



