Java集合框架完全指南
学习目标
- 深入理解Java集合框架的整体架构和设计思想
- 掌握List、Set、Map三大集合体系的核心实现类
- 能够根据业务场景选择合适的集合类型
- 理解集合底层数据结构和性能特点
- 熟练使用集合的常用操作和遍历方式
- 掌握线程安全集合的使用场景
一、概念引入
1.1 为什么需要集合框架?
在实际开发中,我们经常需要存储和操作一组对象:
场景1:用户列表管理
// ✗ 使用数组的问题
User[] users = new User[10]; // 长度固定,无法动态扩容
users[0] = new User("张三");
users[1] = new User("李四");
// 如果用户超过10个怎么办?需要手动扩容,非常麻烦
// ✓ 使用集合
List<User> users = new ArrayList<>();
users.add(new User("张三"));
users.add(new User("李四"));
// 自动扩容,使用方便
场景2:商品去重
// ✗ 使用数组需要手动去重
String[] products = {"手机", "电脑", "手机", "平板"};
// 需要写循环判断重复,代码复杂
// ✓ 使用Set自动去重
Set<String> products = new HashSet<>();
products.add("手机");
products.add("电脑");
products.add("手机"); // 自动忽略重复
products.add("平板");
// 结果:[手机, 电脑, 平板]
场景3:配置信息存储
// ✗ 使用两个数组维护键值对
String[] keys = {"host", "port", "username"};
String[] values = {"localhost", "3306", "root"};
// 需要通过索引关联,容易出错
// ✓ 使用Map存储键值对
Map<String, String> config = new HashMap<>();
config.put("host", "localhost");
config.put("port", "3306");
config.put("username", "root");
// 通过key直接获取value,清晰明了
数组的局限性:
- 长度固定,无法动态扩容
- 只能存储同一类型的数据
- 没有提供便捷的增删改查方法
- 无法自动去重
- 不支持键值对存储
集合框架的优势:
- 动态扩容,自动管理内存
- 提供丰富的操作方法
- 支持泛型,类型安全
- 多种数据结构实现,满足不同需求
- 线程安全和非线程安全版本可选
1.2 集合框架的核心思想
Java集合框架 = 接口 + 实现类 + 算法
设计理念:
- 接口定义规范:Collection、List、Set、Map等接口定义了集合的标准操作
- 实现类提供功能:ArrayList、LinkedList、HashSet、HashMap等提供具体实现
- 算法独立封装:Collections工具类提供排序、查找等算法
集合框架的层次结构:
Iterable<E>
↑
Collection<E>
↑
┌───────────────┼───────────────┐
↑ ↑ ↑
List<E> Set<E> Queue<E>
↑ ↑ ↑
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
↑ ↑ ↑ ↑ ↑ ↑
ArrayList LinkedList HashSet TreeSet PriorityQueue ArrayDeque
Map<K,V>
↑
┌───────────────┼───────────────┐
↑ ↑ ↑
HashMap TreeMap LinkedHashMap
↑
Hashtable
↑
Properties
1.3 集合框架的三大体系
| 体系 | 接口 | 特点 | 常用实现类 |
|---|---|---|---|
| List | 有序、可重复 | 按索引访问 | ArrayList、LinkedList、Vector |
| Set | 无序、不可重复 | 元素唯一 | HashSet、TreeSet、LinkedHashSet |
| Map | 键值对 | key唯一 | HashMap、TreeMap、LinkedHashMap |
二、List集合详解
2.1 List接口概述
List的核心特点:
- 有序:元素按照插入顺序排列
- 可重复:允许存储重复元素
- 索引访问:可以通过索引(下标)访问元素
List接口的常用方法:
public interface List<E> extends Collection<E> {
// 添加元素
boolean add(E e); // 在末尾添加
void add(int index, E element); // 在指定位置插入
// 删除元素
E remove(int index); // 删除指定位置的元素
boolean remove(Object o); // 删除指定元素
// 修改元素
E set(int index, E element); // 替换指定位置的元素
// 查询元素
E get(int index); // 获取指定位置的元素
int indexOf(Object o); // 查找元素第一次出现的位置
int lastIndexOf(Object o); // 查找元素最后一次出现的位置
// 其他操作
int size(); // 获取元素个数
boolean isEmpty(); // 判断是否为空
boolean contains(Object o); // 判断是否包含某元素
void clear(); // 清空集合
// 子列表
List<E> subList(int fromIndex, int toIndex); // 获取子列表
}
2.2 ArrayList详解
ArrayList的核心特点:
- 底层使用动态数组实现
- 查询快:O(1)时间复杂度
- 增删慢:需要移动元素,O(n)时间复杂度
- 非线程安全
ArrayList的底层原理:
ArrayList内部结构:
┌─────────────────────────────────────────┐
│ ArrayList │
├─────────────────────────────────────────┤
│ elementData: Object[] ← 存储元素的数组 │
│ size: int ← 实际元素个数 │
│ DEFAULT_CAPACITY = 10 ← 默认容量 │
└─────────────────────────────────────────┘
初始化:
elementData = {} (空数组)
size = 0
第一次add:
elementData = new Object[10] (扩容到10)
size = 1
扩容机制:
当 size == elementData.length 时
newCapacity = oldCapacity + (oldCapacity >> 1) // 扩容1.5倍
elementData = Arrays.copyOf(elementData, newCapacity)
ArrayList的基本使用:
// 1. 创建ArrayList
List<String> list = new ArrayList<>(); // 默认容量10
List<String> list2 = new ArrayList<>(20); // 指定初始容量
List<String> list3 = new ArrayList<>(list); // 从其他集合创建
// 2. 添加元素
list.add("Java"); // 在末尾添加
list.add("Python");
list.add("C++");
list.add(1, "Go"); // 在索引1处插入
// 结果:[Java, Go, Python, C++]
// 3. 获取元素
String first = list.get(0); // 获取第一个元素:Java
String last = list.get(list.size() - 1); // 获取最后一个元素:C++
// 4. 修改元素
list.set(1, "JavaScript"); // 将索引1的元素改为JavaScript
// 结果:[Java, JavaScript, Python, C++]
// 5. 删除元素
list.remove(0); // 删除索引0的元素
list.remove("Python"); // 删除指定元素
// 结果:[JavaScript, C++]
// 6. 查找元素
int index = list.indexOf("C++"); // 查找元素位置:1
boolean exists = list.contains("Java"); // 判断是否存在:false
// 7. 遍历元素
// 方式1:for循环
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// 方式2:增强for循环
for (String item : list) {
System.out.println(item);
}
// 方式3:Iterator迭代器
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// 方式4:forEach + Lambda
list.forEach(item -> System.out.println(item));
// 方式5:Stream API
list.stream().forEach(System.out::println);
ArrayList的性能分析:
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| get(index) | O(1) | 直接通过数组下标访问 |
| add(e) | O(1) | 在末尾添加,均摊O(1) |
| add(index, e) | O(n) | 需要移动后续元素 |
| remove(index) | O(n) | 需要移动后续元素 |
| contains(o) | O(n) | 需要遍历查找 |
| size() | O(1) | 直接返回size字段 |
ArrayList的使用场景:
// ✓ 适合场景1:频繁查询
List<Product> products = new ArrayList<>();
// 根据索引快速获取商品信息
Product p = products.get(5);
// ✓ 适合场景2:在末尾添加元素
List<String> logs = new ArrayList<>();
logs.add("日志1"); // 在末尾添加,性能好
logs.add("日志2");
// ✗ 不适合场景1:频繁在中间插入删除
List<Integer> numbers = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
numbers.add(0, i); // 每次都在开头插入,性能差
}
// ✗ 不适合场景2:大量数据的频繁增删
List<String> queue = new ArrayList<>();
queue.add(0, "新元素"); // 在开头插入
queue.remove(0); // 删除第一个元素
// 这种场景应该使用LinkedList
2.3 LinkedList详解
LinkedList的核心特点:
- 底层使用双向链表实现
- 增删快:O(1)时间复杂度(在已知节点位置的情况下)
- 查询慢:O(n)时间复杂度,需要遍历
- 非线程安全
- 实现了Deque接口,可以作为队列、栈使用
LinkedList的底层原理:
LinkedList内部结构:
┌─────────────────────────────────────────┐
│ LinkedList │
├─────────────────────────────────────────┤
│ first: Node<E> ← 指向第一个节点 │
│ last: Node<E> ← 指向最后一个节点 │
│ size: int ← 元素个数 │
└─────────────────────────────────────────┘
Node节点结构:
┌─────────────────────────────────────────┐
│ Node<E> │
├─────────────────────────────────────────┤
│ item: E ← 存储的元素 │
│ next: Node<E> ← 指向下一个节点 │
│ prev: Node<E> ← 指向上一个节点 │
└─────────────────────────────────────────┘
双向链表示意图:
null ← [Node1] ↔ [Node2] ↔ [Node3] → null
↑ ↑
first last
LinkedList的基本使用:
// 1. 创建LinkedList
List<String> list = new LinkedList<>();
// 2. 添加元素
list.add("A"); // 在末尾添加
list.add("B");
list.add("C");
list.add(1, "X"); // 在索引1处插入
// 结果:[A, X, B, C]
// 3. 作为双端队列使用
LinkedList<String> deque = new LinkedList<>();
// 在头部操作
deque.addFirst("头部元素"); // 在开头添加
deque.offerFirst("头部元素2"); // 在开头添加(推荐)
String first = deque.getFirst(); // 获取第一个元素
String poll = deque.pollFirst(); // 获取并删除第一个元素
// 在尾部操作
deque.addLast("尾部元素"); // 在末尾添加
deque.offerLast("尾部元素2"); // 在末尾添加(推荐)
String last = deque.getLast(); // 获取最后一个元素
String pollLast = deque.pollLast(); // 获取并删除最后一个元素
// 4. 作为栈使用
LinkedList<String> stack = new LinkedList<>();
stack.push("元素1"); // 入栈
stack.push("元素2");
stack.push("元素3");
String top = stack.pop(); // 出栈:元素3
String peek = stack.peek(); // 查看栈顶:元素2
// 5. 作为队列使用
LinkedList<String> queue = new LinkedList<>();
queue.offer("任务1"); // 入队
queue.offer("任务2");
queue.offer("任务3");
String task = queue.poll(); // 出队:任务1
String head = queue.peek(); // 查看队首:任务2
LinkedList vs ArrayList对比:
| 维度 | ArrayList | LinkedList |
|---|---|---|
| 底层结构 | 动态数组 | 双向链表 |
| 随机访问 | O(1) 快 | O(n) 慢 |
| 插入删除(末尾) | O(1) 快 | O(1) 快 |
| 插入删除(中间) | O(n) 慢 | O(1) 快(已知节点) |
| 内存占用 | 连续内存 | 不连续,额外存储指针 |
| 适用场景 | 查询多 | 增删多 |
LinkedList的使用场景:
// ✓ 适合场景1:频繁在头尾插入删除
LinkedList<String> queue = new LinkedList<>();
queue.addFirst("新任务"); // 在开头插入,O(1)
queue.removeLast(); // 删除最后一个,O(1)
// ✓ 适合场景2:实现队列或栈
// 队列:先进先出
LinkedList<Order> orderQueue = new LinkedList<>();
orderQueue.offer(new Order()); // 入队
Order order = orderQueue.poll(); // 出队
// 栈:后进先出
LinkedList<String> stack = new LinkedList<>();
stack.push("A"); // 入栈
String top = stack.pop(); // 出栈
// ✗ 不适合场景:频繁随机访问
LinkedList<Integer> numbers = new LinkedList<>();
for (int i = 0; i < 10000; i++) {
numbers.add(i);
}
// 随机访问性能差
int value = numbers.get(5000); // 需要遍历5000个节点
2.4 Vector详解
Vector的核心特点:
- 底层使用动态数组实现(与ArrayList相同)
- 线程安全:所有方法都加了synchronized
- 扩容机制:默认扩容2倍(ArrayList是1.5倍)
- 性能较差:因为同步开销
- 已过时,不推荐使用
Vector vs ArrayList:
// Vector:线程安全但性能差
Vector<String> vector = new Vector<>();
vector.add("A"); // synchronized方法,线程安全
// ArrayList:非线程安全但性能好
ArrayList<String> list = new ArrayList<>();
list.add("A"); // 非synchronized,性能好
// 如果需要线程安全的List,推荐使用:
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
// 或者使用并发包中的CopyOnWriteArrayList
List<String> cowList = new CopyOnWriteArrayList<>();
2.5 List的选择决策树
需要List集合
↓
是否需要线程安全?
├─ 是 → CopyOnWriteArrayList(读多写少)
│ 或 Collections.synchronizedList()
│
└─ 否 → 是否频繁随机访问?
├─ 是 → ArrayList(查询多)
│
└─ 否 → 是否频繁在头尾插入删除?
├─ 是 → LinkedList(增删多)
│
└─ 否 → ArrayList(通用场景)
三、Set集合详解
3.1 Set接口概述
Set的核心特点:
- 无序:元素没有固定顺序(HashSet)或按某种规则排序(TreeSet)
- 不可重复:不允许存储重复元素
- 无索引:不能通过索引访问元素
Set接口的常用方法:
public interface Set<E> extends Collection<E> {
// 添加元素
boolean add(E e); // 添加元素,重复则返回false
// 删除元素
boolean remove(Object o); // 删除指定元素
// 查询元素
boolean contains(Object o); // 判断是否包含某元素
int size(); // 获取元素个数
boolean isEmpty(); // 判断是否为空
// 集合操作
void clear(); // 清空集合
// 注意:Set没有get(index)方法,因为没有索引
}
Set的去重原理:
Set如何判断元素重复?
↓
1. 先调用 hashCode() 方法获取哈希值
↓
2. 比较哈希值是否相同
├─ 不同 → 不重复,直接添加
│
└─ 相同 → 调用 equals() 方法比较内容
├─ 不同 → 不重复,添加(哈希冲突)
│
└─ 相同 → 重复,不添加
3.2 HashSet详解
HashSet的核心特点:
- 底层使用HashMap实现(元素作为HashMap的key)
- 无序:不保证元素顺序
- 不可重复:通过hashCode和equals判断
- 允许null值
- 非线程安全
- 查询快:O(1)时间复杂度
HashSet的底层原理:
HashSet内部结构:
┌─────────────────────────────────────────┐
│ HashSet<E> │
├─────────────────────────────────────────┤
│ map: HashMap<E, Object> │
│ ↑ │
│ └─ 元素存储在HashMap的key中 │
│ value统一使用PRESENT对象 │
└─────────────────────────────────────────┘
添加元素的过程:
set.add(element)
↓
map.put(element, PRESENT)
↓
计算element的hashCode
↓
根据hashCode确定存储位置
↓
如果位置为空,直接存储
如果位置不为空,调用equals比较
├─ equals返回true → 重复,不添加
└─ equals返回false → 不重复,添加(链表或红黑树)
HashSet的基本使用:
// 1. 创建HashSet
Set<String> set = new HashSet<>();
// 2. 添加元素
set.add("Java");
set.add("Python");
set.add("C++");
set.add("Java"); // 重复元素,不会添加
// 结果:[Java, Python, C++](顺序不确定)
// 3. 删除元素
set.remove("Python");
// 结果:[Java, C++]
// 4. 查询元素
boolean exists = set.contains("Java"); // true
int size = set.size(); // 2
// 5. 遍历元素
// 方式1:增强for循环
for (String item : set) {
System.out.println(item);
}
// 方式2:Iterator迭代器
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// 方式3:forEach + Lambda
set.forEach(item -> System.out.println(item));
// 方式4:Stream API
set.stream().forEach(System.out::println);
// 6. 集合运算
Set<Integer> set1 = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
Set<Integer> set2 = new HashSet<>(Arrays.asList(4, 5, 6, 7, 8));
// 并集
Set<Integer> union = new HashSet<>(set1);
union.addAll(set2);
// 结果:[1, 2, 3, 4, 5, 6, 7, 8]
// 交集
Set<Integer> intersection = new HashSet<>(set1);
intersection.retainAll(set2);
// 结果:[4, 5]
// 差集
Set<Integer> difference = new HashSet<>(set1);
difference.removeAll(set2);
// 结果:[1, 2, 3]
自定义对象的去重:
// 自定义Student类
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// 必须重写hashCode和equals方法
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Student student = (Student) obj;
return age == student.age && Objects.equals(name, student.name);
}
}
// 使用HashSet存储Student
Set<Student> students = new HashSet<>();
students.add(new Student("张三", 20));
students.add(new Student("李四", 21));
students.add(new Student("张三", 20)); // 重复,不会添加
// 结果:只有2个学生
HashSet的使用场景:
// ✓ 适合场景1:数据去重
List<String> list = Arrays.asList("A", "B", "A", "C", "B");
Set<String> uniqueSet = new HashSet<>(list);
// 结果:[A, B, C]
// ✓ 适合场景2:快速查找
Set<String> blacklist = new HashSet<>(Arrays.asList("user1", "user2"));
if (blacklist.contains(username)) {
// 用户在黑名单中,O(1)时间复杂度
}
// ✓ 适合场景3:集合运算
Set<String> tags1 = new HashSet<>(Arrays.asList("Java", "Spring", "MySQL"));
Set<String> tags2 = new HashSet<>(Arrays.asList("Python", "Django", "MySQL"));
// 找出共同标签
tags1.retainAll(tags2); // [MySQL]
3.3 TreeSet详解
TreeSet的核心特点:
- 底层使用红黑树实现(TreeMap)
- 有序:元素按照自然顺序或自定义顺序排序
- 不可重复:通过compareTo或Comparator判断
- 不允许null值
- 非线程安全
- 查询:O(log n)时间复杂度
TreeSet的排序方式:
TreeSet的排序
↓
方式1:自然排序(Comparable)
- 元素类实现Comparable接口
- 重写compareTo方法
方式2:定制排序(Comparator)
- 创建TreeSet时传入Comparator
- 实现compare方法
TreeSet的基本使用:
// 1. 自然排序(数字、字符串)
Set<Integer> numbers = new TreeSet<>();
numbers.add(5);
numbers.add(2);
numbers.add(8);
numbers.add(1);
// 结果:[1, 2, 5, 8](自动排序)
Set<String> names = new TreeSet<>();
names.add("张三");
names.add("李四");
names.add("王五");
// 结果:按字典序排序
// 2. 自定义排序(Comparator)
// 降序排列
Set<Integer> descSet = new TreeSet<>((a, b) -> b - a);
descSet.add(5);
descSet.add(2);
descSet.add(8);
// 结果:[8, 5, 2]
// 按字符串长度排序
Set<String> lengthSet = new TreeSet<>((s1, s2) -> {
int lenCompare = Integer.compare(s1.length(), s2.length());
return lenCompare != 0 ? lenCompare : s1.compareTo(s2);
});
lengthSet.add("Java");
lengthSet.add("C");
lengthSet.add("Python");
// 结果:[C, Java, Python]
// 3. 自定义对象排序
public class Student implements Comparable<Student> {
private String name;
private int score;
@Override
public int compareTo(Student other) {
// 按分数降序排列
return Integer.compare(other.score, this.score);
}
}
Set<Student> students = new TreeSet<>();
students.add(new Student("张三", 85));
students.add(new Student("李四", 92));
students.add(new Student("王五", 78));
// 结果:按分数降序排列
// 4. TreeSet的范围操作
TreeSet<Integer> set = new TreeSet<>(Arrays.asList(1, 3, 5, 7, 9));
// 获取子集
SortedSet<Integer> subSet = set.subSet(3, 8); // [3, 5, 7]
SortedSet<Integer> headSet = set.headSet(5); // [1, 3]
SortedSet<Integer> tailSet = set.tailSet(5); // [5, 7, 9]
// 获取边界元素
Integer first = set.first(); // 1
Integer last = set.last(); // 9
// 获取相邻元素
Integer lower = set.lower(5); // 3(小于5的最大元素)
Integer higher = set.higher(5); // 7(大于5的最小元素)
Integer floor = set.floor(6); // 5(小于等于6的最大元素)
Integer ceiling = set.ceiling(6); // 7(大于等于6的最小元素)
TreeSet vs HashSet对比:
| 维度 | HashSet | TreeSet |
|---|---|---|
| 底层结构 | HashMap | 红黑树 |
| 是否有序 | 无序 | 有序 |
| 时间复杂度 | O(1) | O(log n) |
| 是否允许null | 允许 | 不允许 |
| 比较方式 | hashCode + equals | compareTo/Comparator |
| 适用场景 | 去重、快速查找 | 需要排序 |
3.4 LinkedHashSet详解
LinkedHashSet的核心特点:
- 底层使用HashMap + 双向链表实现
- 有序:按照插入顺序排列
- 不可重复:通过hashCode和equals判断
- 允许null值
- 非线程安全
LinkedHashSet的使用:
// LinkedHashSet保持插入顺序
Set<String> set = new LinkedHashSet<>();
set.add("Java");
set.add("Python");
set.add("C++");
set.add("Go");
// 结果:[Java, Python, C++, Go](保持插入顺序)
// 对比HashSet(无序)
Set<String> hashSet = new HashSet<>();
hashSet.add("Java");
hashSet.add("Python");
hashSet.add("C++");
hashSet.add("Go");
// 结果:顺序不确定
3.5 Set的选择决策树
需要Set集合
↓
是否需要排序?
├─ 是 → TreeSet(自动排序)
│
└─ 否 → 是否需要保持插入顺序?
├─ 是 → LinkedHashSet(插入顺序)
│
└─ 否 → HashSet(无序,性能最好)
四、Map集合详解
4.1 Map接口概述
Map的核心特点:
- 键值对存储:每个元素包含key和value
- key唯一:不允许重复的key
- value可重复:允许重复的value
- key-value一一对应:一个key对应一个value
Map接口的常用方法:
public interface Map<K, V> {
// 添加/修改
V put(K key, V value); // 添加键值对,返回旧值
void putAll(Map<? extends K, ? extends V> m); // 批量添加
V putIfAbsent(K key, V value); // key不存在时才添加
// 删除
V remove(Object key); // 删除指定key
boolean remove(Object key, Object value); // key和value都匹配才删除
// 查询
V get(Object key); // 获取value
V getOrDefault(Object key, V defaultValue); // 获取value,不存在返回默认值
boolean containsKey(Object key); // 判断是否包含key
boolean containsValue(Object value); // 判断是否包含value
int size(); // 获取键值对个数
boolean isEmpty(); // 判断是否为空
// 批量操作
void clear(); // 清空Map
Set<K> keySet(); // 获取所有key的Set
Collection<V> values(); // 获取所有value的Collection
Set<Map.Entry<K, V>> entrySet(); // 获取所有键值对的Set
// Java 8新增方法
V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction);
V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction);
V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction);
V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction);
void forEach(BiConsumer<? super K, ? super V> action);
void replaceAll(BiFunction<? super K, ? super V, ? extends V> function);
}
4.2 HashMap详解
HashMap的核心特点:
- 底层使用数组 + 链表 + 红黑树实现
- 无序:不保证key的顺序
- key和value都允许null
- 非线程安全
- 查询快:O(1)时间复杂度(理想情况)
HashMap的底层原理:
HashMap内部结构(JDK 1.8):
┌─────────────────────────────────────────────────────────┐
│ HashMap │
├─────────────────────────────────────────────────────────┤
│ table: Node<K,V>[] ← 哈希表(数组) │
│ size: int ← 键值对个数 │
│ threshold: int ← 扩容阈值 = capacity * loadFactor │
│ loadFactor: float ← 负载因子,默认0.75 │
│ DEFAULT_INITIAL_CAPACITY = 16 ← 默认初始容量 │
└─────────────────────────────────────────────────────────┘
Node节点结构:
┌─────────────────────────────────────────┐
│ Node<K,V> │
├─────────────────────────────────────────┤
│ hash: int ← key的哈希值 │
│ key: K ← 键 │
│ value: V ← 值 │
│ next: Node<K,V> ← 指向下一个节点 │
└─────────────────────────────────────────┘
存储结构示意图:
table数组
┌────┬────┬────┬────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │
└────┴────┴────┴────┴────┴────┴────┴────┘
│ │ │ │
↓ ↓ ↓ ↓
Node Node Node Node
│ │
↓ ↓
Node 红黑树
│ (链表长度≥8)
↓
Node
HashMap的put过程:
put(key, value)的执行流程:
1. 计算key的hash值
hash = hash(key)
2. 根据hash值计算数组索引
index = (table.length - 1) & hash
3. 判断table[index]位置
├─ 如果为null
│ └─ 直接创建新Node放入
│
└─ 如果不为null(发生哈希冲突)
├─ 如果key已存在
│ └─ 替换旧value,返回旧value
│
└─ 如果key不存在
├─ 如果是链表
│ ├─ 链表长度 < 8
│ │ └─ 添加到链表末尾
│ │
│ └─ 链表长度 ≥ 8
│ └─ 转换为红黑树
│
└─ 如果是红黑树
└─ 添加到红黑树中
4. 判断是否需要扩容
if (++size > threshold)
resize() // 扩容为原来的2倍
HashMap的基本使用:
// 1. 创建HashMap
Map<String, Integer> map = new HashMap<>(); // 默认容量16
Map<String, Integer> map2 = new HashMap<>(32); // 指定初始容量
Map<String, Integer> map3 = new HashMap<>(map); // 从其他Map创建
// 2. 添加键值对
map.put("Java", 95);
map.put("Python", 88);
map.put("C++", 92);
map.put("Java", 98); // key重复,会覆盖旧值
// 结果:{Java=98, Python=88, C++=92}
// 3. 获取value
Integer score = map.get("Java"); // 98
Integer score2 = map.get("Go"); // null(key不存在)
Integer score3 = map.getOrDefault("Go", 0); // 0(返回默认值)
// 4. 判断key/value是否存在
boolean hasJava = map.containsKey("Java"); // true
boolean has100 = map.containsValue(100); // false
// 5. 删除键值对
map.remove("Python"); // 删除key为Python的键值对
map.remove("C++", 90); // key和value都匹配才删除(不会删除)
map.remove("C++", 92); // 删除成功
// 6. 遍历Map
// 方式1:遍历keySet
for (String key : map.keySet()) {
Integer value = map.get(key);
System.out.println(key + " = " + value);
}
// 方式2:遍历entrySet(推荐,性能好)
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + " = " + value);
}
// 方式3:遍历values
for (Integer value : map.values()) {
System.out.println(value);
}
// 方式4:forEach + Lambda(推荐)
map.forEach((key, value) -> {
System.out.println(key + " = " + value);
});
// 方式5:Stream API
map.entrySet().stream()
.forEach(entry -> System.out.println(entry.getKey() + " = " + entry.getValue()));
// 7. Java 8新增方法
// putIfAbsent:key不存在时才添加
map.putIfAbsent("Go", 85); // 添加成功
map.putIfAbsent("Java", 100); // key已存在,不添加
// computeIfAbsent:key不存在时计算并添加
map.computeIfAbsent("Rust", k -> k.length() * 10); // Rust=40
// computeIfPresent:key存在时重新计算value
map.computeIfPresent("Java", (k, v) -> v + 5); // Java=103
// merge:合并value
map.merge("Java", 10, (oldVal, newVal) -> oldVal + newVal); // Java=113
// replace:替换value
map.replace("Python", 88, 90); // 旧值匹配才替换
map.replace("Python", 95); // 直接替换
HashMap的使用场景:
// ✓ 适合场景1:缓存数据
Map<String, User> userCache = new HashMap<>();
userCache.put("user123", new User("张三"));
User user = userCache.get("user123"); // O(1)快速获取
// ✓ 适合场景2:统计频次
String text = "hello world hello java";
Map<String, Integer> wordCount = new HashMap<>();
for (String word : text.split(" ")) {
wordCount.put(word, wordCount.getOrDefault(word, 0) + 1);
}
// 结果:{hello=2, world=1, java=1}
// ✓ 适合场景3:配置信息
Map<String, String> config = new HashMap<>();
config.put("db.host", "localhost");
config.put("db.port", "3306");
config.put("db.username", "root");
// ✓ 适合场景4:分组数据
List<Student> students = Arrays.asList(
new Student("张三", "一班"),
new Student("李四", "二班"),
new Student("王五", "一班")
);
Map<String, List<Student>> groupByClass = new HashMap<>();
for (Student student : students) {
groupByClass.computeIfAbsent(student.getClassName(), k -> new ArrayList<>())
.add(student);
}
// 结果:{一班=[张三, 王五], 二班=[李四]}
4.3 LinkedHashMap详解
LinkedHashMap的核心特点:
- 底层使用HashMap + 双向链表实现
- 有序:按照插入顺序或访问顺序排列
- 可以实现LRU缓存
- 其他特性与HashMap相同
LinkedHashMap的使用:
// 1. 按插入顺序(默认)
Map<String, Integer> map = new LinkedHashMap<>();
map.put("C", 3);
map.put("A", 1);
map.put("B", 2);
// 遍历顺序:C, A, B(保持插入顺序)
// 2. 按访问顺序(LRU)
Map<String, Integer> lruMap = new LinkedHashMap<>(16, 0.75f, true);
// ↑
// accessOrder=true
lruMap.put("A", 1);
lruMap.put("B", 2);
lruMap.put("C", 3);
lruMap.get("A"); // 访问A,A移到末尾
// 遍历顺序:B, C, A
// 3. 实现LRU缓存
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private int capacity;
public LRUCache(int capacity) {
super(capacity, 0.75f, true);
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacity; // 超过容量时删除最老的元素
}
}
// 使用LRU缓存
LRUCache<String, String> cache = new LRUCache<>(3);
cache.put("1", "A");
cache.put("2", "B");
cache.put("3", "C");
cache.put("4", "D"); // 超过容量,删除最老的"1"
// 结果:{2=B, 3=C, 4=D}
4.4 TreeMap详解
TreeMap的核心特点:
- 底层使用红黑树实现
- 有序:按照key的自然顺序或自定义顺序排序
- key不允许null,value允许null
- 非线程安全
- 查询:O(log n)时间复杂度
TreeMap的使用:
// 1. 自然排序
Map<Integer, String> map = new TreeMap<>();
map.put(3, "C");
map.put(1, "A");
map.put(2, "B");
// 遍历顺序:1=A, 2=B, 3=C(按key排序)
// 2. 自定义排序
Map<String, Integer> scoreMap = new TreeMap<>((s1, s2) -> s2.compareTo(s1));
scoreMap.put("张三", 85);
scoreMap.put("李四", 92);
scoreMap.put("王五", 78);
// 按key降序排列
// 3. 范围操作
TreeMap<Integer, String> treeMap = new TreeMap<>();
treeMap.put(1, "A");
treeMap.put(3, "C");
treeMap.put(5, "E");
treeMap.put(7, "G");
treeMap.put(9, "I");
// 获取子Map
SortedMap<Integer, String> subMap = treeMap.subMap(3, 8); // {3=C, 5=E, 7=G}
SortedMap<Integer, String> headMap = treeMap.headMap(5); // {1=A, 3=C}
SortedMap<Integer, String> tailMap = treeMap.tailMap(5); // {5=E, 7=G, 9=I}
// 获取边界key
Integer firstKey = treeMap.firstKey(); // 1
Integer lastKey = treeMap.lastKey(); // 9
// 获取相邻key
Integer lowerKey = treeMap.lowerKey(5); // 3
Integer higherKey = treeMap.higherKey(5); // 7
4.5 Hashtable详解
Hashtable的核心特点:
- 底层使用数组 + 链表实现
- 线程安全:所有方法都加了synchronized
- key和value都不允许null
- 已过时,不推荐使用
Hashtable vs HashMap:
| 维度 | HashMap | Hashtable |
|---|---|---|
| 线程安全 | 否 | 是 |
| null值 | 允许 | 不允许 |
| 性能 | 快 | 慢 |
| 推荐使用 | 是 | 否 |
替代方案:
// 不推荐:Hashtable
Hashtable<String, Integer> table = new Hashtable<>();
// 推荐方案1:Collections.synchronizedMap
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
// 推荐方案2:ConcurrentHashMap(性能更好)
Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
4.6 Map的选择决策树
需要Map集合
↓
是否需要线程安全?
├─ 是 → ConcurrentHashMap(高并发)
│
└─ 否 → 是否需要排序?
├─ 是 → TreeMap(按key排序)
│
└─ 否 → 是否需要保持插入顺序?
├─ 是 → LinkedHashMap(插入顺序)
│
└─ 否 → HashMap(无序,性能最好)
五、线程安全集合
5.1 为什么需要线程安全集合?
多线程环境下的问题:
// 问题示例:多线程操作ArrayList
List<Integer> list = new ArrayList<>();
// 线程1
new Thread(() -> {
for (int i = 0; i < 1000; i++) {
list.add(i);
}
}).start();
// 线程2
new Thread(() -> {
for (int i = 1000; i < 2000; i++) {
list.add(i);
}
}).start();
// 可能出现的问题:
// 1. 数据丢失
// 2. ArrayIndexOutOfBoundsException
// 3. 结果不确定
5.2 线程安全集合的实现方式
方式1:使用Collections工具类
// 同步List
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
// 同步Set
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
// 同步Map
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
// 注意:遍历时仍需手动同步
synchronized (syncList) {
for (String item : syncList) {
System.out.println(item);
}
}
方式2:使用并发包中的集合
// CopyOnWriteArrayList:读多写少
List<String> cowList = new CopyOnWriteArrayList<>();
// CopyOnWriteArraySet:读多写少
Set<String> cowSet = new CopyOnWriteArraySet<>();
// ConcurrentHashMap:高并发
Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
// ConcurrentSkipListMap:有序 + 高并发
Map<String, Integer> skipListMap = new ConcurrentSkipListMap<>();
5.3 CopyOnWriteArrayList详解
核心原理:
- 写时复制(Copy-On-Write)
- 读操作不加锁,写操作加锁并复制数组
- 适合读多写少的场景
// CopyOnWriteArrayList的使用
List<String> list = new CopyOnWriteArrayList<>();
// 写操作(加锁)
list.add("A"); // 复制数组,添加元素,替换原数组
// 读操作(不加锁)
String item = list.get(0); // 直接读取,无锁
// 适用场景:监听器列表
List<EventListener> listeners = new CopyOnWriteArrayList<>();
listeners.add(new EventListener()); // 添加监听器(写少)
// 触发事件时遍历监听器(读多)
for (EventListener listener : listeners) {
listener.onEvent();
}
5.4 ConcurrentHashMap详解
核心原理(JDK 1.8):
- 使用CAS + synchronized实现
- 分段锁机制,提高并发度
- 读操作无锁,写操作锁定单个节点
// ConcurrentHashMap的使用
Map<String, Integer> map = new ConcurrentHashMap<>();
// 线程安全的操作
map.put("key", 1);
Integer value = map.get("key");
// 原子操作
map.putIfAbsent("key", 2); // key不存在时才添加
map.computeIfAbsent("key", k -> k.length()); // 原子计算
// 适用场景:高并发缓存
ConcurrentHashMap<String, User> userCache = new ConcurrentHashMap<>();
// 多线程安全地读写缓存
userCache.put("user123", new User());
User user = userCache.get("user123");
5.5 线程安全集合的选择
| 场景 | 推荐集合 | 原因 |
|---|---|---|
| 读多写少的List | CopyOnWriteArrayList | 读操作无锁,性能好 |
| 高并发的Map | ConcurrentHashMap | 分段锁,并发度高 |
| 需要排序的并发Map | ConcurrentSkipListMap | 有序 + 线程安全 |
| 简单的线程安全需求 | Collections.synchronizedXxx | 实现简单 |
六、集合工具类
6.1 Collections工具类
Collections提供的常用方法:
// 1. 排序
List<Integer> list = new ArrayList<>(Arrays.asList(3, 1, 4, 1, 5, 9));
Collections.sort(list); // 升序排序:[1, 1, 3, 4, 5, 9]
Collections.sort(list, Collections.reverseOrder()); // 降序:[9, 5, 4, 3, 1, 1]
// 2. 查找
int index = Collections.binarySearch(list, 4); // 二分查找:2
int max = Collections.max(list); // 最大值:9
int min = Collections.min(list); // 最小值:1
// 3. 反转
Collections.reverse(list); // 反转列表
// 4. 打乱
Collections.shuffle(list); // 随机打乱
// 5. 填充
Collections.fill(list, 0); // 用0填充所有元素
// 6. 复制
List<Integer> dest = new ArrayList<>(Collections.nCopies(list.size(), 0));
Collections.copy(dest, list); // 复制list到dest
// 7. 替换
Collections.replaceAll(list, 1, 10); // 将所有1替换为10
// 8. 频次统计
int frequency = Collections.frequency(list, 1); // 统计1出现的次数
// 9. 不可变集合
List<String> immutableList = Collections.unmodifiableList(list);
// immutableList.add("X"); // 抛出UnsupportedOperationException
// 10. 空集合
List<String> emptyList = Collections.emptyList();
Set<String> emptySet = Collections.emptySet();
Map<String, Integer> emptyMap = Collections.emptyMap();
// 11. 单元素集合
List<String> singletonList = Collections.singletonList("A");
Set<String> singletonSet = Collections.singleton("A");
Map<String, Integer> singletonMap = Collections.singletonMap("key", 1);
6.2 Arrays工具类
Arrays提供的常用方法:
// 1. 数组转List
String[] array = {"A", "B", "C"};
List<String> list = Arrays.asList(array);
// 注意:返回的List是固定大小的,不能add/remove
// 2. 排序
int[] numbers = {3, 1, 4, 1, 5, 9};
Arrays.sort(numbers); // 升序排序
// 3. 二分查找
int index = Arrays.binarySearch(numbers, 4); // 查找4的位置
// 4. 填充
Arrays.fill(numbers, 0); // 用0填充数组
// 5. 复制
int[] copy = Arrays.copyOf(numbers, numbers.length); // 复制数组
int[] range = Arrays.copyOfRange(numbers, 0, 3); // 复制指定范围
// 6. 比较
boolean equals = Arrays.equals(numbers, copy); // 比较两个数组
// 7. 转字符串
String str = Arrays.toString(numbers); // [0, 0, 0, 0, 0, 0]
// 8. Stream操作
int sum = Arrays.stream(numbers).sum(); // 求和
double average = Arrays.stream(numbers).average().orElse(0); // 平均值
七、集合的遍历方式对比
7.1 List的遍历方式
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D", "E"));
// 方式1:普通for循环(推荐用于需要索引的场景)
for (int i = 0; i < list.size(); i++) {
String item = list.get(i);
System.out.println(i + ": " + item);
}
// 方式2:增强for循环(推荐用于简单遍历)
for (String item : list) {
System.out.println(item);
}
// 方式3:Iterator迭代器(推荐用于需要删除元素的场景)
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (item.equals("B")) {
iterator.remove(); // 安全删除
}
}
// 方式4:forEach + Lambda(推荐用于简洁代码)
list.forEach(item -> System.out.println(item));
list.forEach(System.out::println); // 方法引用
// 方式5:Stream API(推荐用于复杂操作)
list.stream()
.filter(item -> item.length() > 1)
.map(String::toLowerCase)
.forEach(System.out::println);
7.2 Map的遍历方式
Map<String, Integer> map = new HashMap<>();
map.put("Java", 95);
map.put("Python", 88);
map.put("C++", 92);
// 方式1:遍历keySet
for (String key : map.keySet()) {
Integer value = map.get(key);
System.out.println(key + " = " + value);
}
// 方式2:遍历entrySet(推荐,性能最好)
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + " = " + value);
}
// 方式3:遍历values
for (Integer value : map.values()) {
System.out.println(value);
}
// 方式4:forEach + Lambda(推荐)
map.forEach((key, value) -> {
System.out.println(key + " = " + value);
});
// 方式5:Stream API
map.entrySet().stream()
.filter(entry -> entry.getValue() > 90)
.forEach(entry -> System.out.println(entry.getKey()));
7.3 遍历时删除元素
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));
// ✗ 错误方式1:增强for循环中删除
for (String item : list) {
if (item.equals("B")) {
list.remove(item); // ConcurrentModificationException
}
}
// ✗ 错误方式2:普通for循环中删除(索引问题)
for (int i = 0; i < list.size(); i++) {
if (list.get(i).equals("B")) {
list.remove(i); // 删除后索引变化,可能跳过元素
}
}
// ✓ 正确方式1:使用Iterator
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (item.equals("B")) {
iterator.remove(); // 安全删除
}
}
// ✓ 正确方式2:倒序遍历
for (int i = list.size() - 1; i >= 0; i--) {
if (list.get(i).equals("B")) {
list.remove(i);
}
}
// ✓ 正确方式3:removeIf(Java 8)
list.removeIf(item -> item.equals("B"));
// ✓ 正确方式4:Stream过滤(创建新集合)
List<String> filtered = list.stream()
.filter(item -> !item.equals("B"))
.collect(Collectors.toList());
八、集合的性能对比
8.1 时间复杂度对比表
List实现类:
| 操作 | ArrayList | LinkedList | Vector |
|---|---|---|---|
| get(index) | O(1) | O(n) | O(1) |
| add(e) | O(1)* | O(1) | O(1)* |
| add(index, e) | O(n) | O(1)** | O(n) |
| remove(index) | O(n) | O(1)** | O(n) |
| contains(e) | O(n) | O(n) | O(n) |
*均摊时间复杂度
**已知节点位置时
Set实现类:
| 操作 | HashSet | TreeSet | LinkedHashSet |
|---|---|---|---|
| add(e) | O(1) | O(log n) | O(1) |
| remove(e) | O(1) | O(log n) | O(1) |
| contains(e) | O(1) | O(log n) | O(1) |
Map实现类:
| 操作 | HashMap | TreeMap | LinkedHashMap |
|---|---|---|---|
| put(k, v) | O(1) | O(log n) | O(1) |
| get(k) | O(1) | O(log n) | O(1) |
| remove(k) | O(1) | O(log n) | O(1) |
| containsKey(k) | O(1) | O(log n) | O(1) |
8.2 内存占用对比
内存占用(从小到大):
ArrayList < LinkedList
- ArrayList:只存储元素
- LinkedList:存储元素 + 前后指针
HashSet < TreeSet < LinkedHashSet
- HashSet:HashMap的key
- TreeSet:红黑树节点
- LinkedHashSet:HashMap + 双向链表
HashMap < TreeMap < LinkedHashMap
- HashMap:数组 + 链表/红黑树
- TreeMap:红黑树节点
- LinkedHashMap:HashMap + 双向链表
8.3 性能测试示例
public class CollectionPerformanceTest {
public static void main(String[] args) {
int size = 100000;
// 测试ArrayList vs LinkedList
testListAdd(size);
testListGet(size);
testListRemove(size);
// 测试HashSet vs TreeSet
testSetAdd(size);
testSetContains(size);
// 测试HashMap vs TreeMap
testMapPut(size);
testMapGet(size);
}
// ArrayList vs LinkedList:添加元素
private static void testListAdd(int size) {
// ArrayList在末尾添加
long start = System.currentTimeMillis();
List<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < size; i++) {
arrayList.add(i);
}
System.out.println("ArrayList add: " + (System.currentTimeMillis() - start) + "ms");
// LinkedList在末尾添加
start = System.currentTimeMillis();
List<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < size; i++) {
linkedList.add(i);
}
System.out.println("LinkedList add: " + (System.currentTimeMillis() - start) + "ms");
// ArrayList在开头添加(性能差)
start = System.currentTimeMillis();
arrayList = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
arrayList.add(0, i);
}
System.out.println("ArrayList add(0): " + (System.currentTimeMillis() - start) + "ms");
// LinkedList在开头添加(性能好)
start = System.currentTimeMillis();
linkedList = new LinkedList<>();
for (int i = 0; i < 10000; i++) {
linkedList.add(0, i);
}
System.out.println("LinkedList add(0): " + (System.currentTimeMillis() - start) + "ms");
}
// ArrayList vs LinkedList:随机访问
private static void testListGet(int size) {
List<Integer> arrayList = new ArrayList<>();
List<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < size; i++) {
arrayList.add(i);
linkedList.add(i);
}
// ArrayList随机访问(性能好)
long start = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
arrayList.get(i);
}
System.out.println("ArrayList get: " + (System.currentTimeMillis() - start) + "ms");
// LinkedList随机访问(性能差)
start = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
linkedList.get(i);
}
System.out.println("LinkedList get: " + (System.currentTimeMillis() - start) + "ms");
}
}
九、集合的常见使用场景
9.1 数据去重
// 场景:从列表中去除重复元素
List<String> list = Arrays.asList("A", "B", "A", "C", "B", "D");
// 方式1:使用HashSet
Set<String> set = new HashSet<>(list);
List<String> uniqueList = new ArrayList<>(set);
// 结果:[A, B, C, D](无序)
// 方式2:使用LinkedHashSet(保持顺序)
Set<String> linkedSet = new LinkedHashSet<>(list);
List<String> orderedUniqueList = new ArrayList<>(linkedSet);
// 结果:[A, B, C, D](保持插入顺序)
// 方式3:使用Stream
List<String> streamUnique = list.stream()
.distinct()
.collect(Collectors.toList());
9.2 统计频次
// 场景:统计单词出现次数
String text = "hello world hello java world hello";
String[] words = text.split(" ");
// 方式1:使用HashMap
Map<String, Integer> wordCount = new HashMap<>();
for (String word : words) {
wordCount.put(word, wordCount.getOrDefault(word, 0) + 1);
}
// 结果:{hello=3, world=2, java=1}
// 方式2:使用computeIfAbsent
Map<String, Integer> wordCount2 = new HashMap<>();
for (String word : words) {
wordCount2.merge(word, 1, Integer::sum);
}
// 方式3:使用Stream
Map<String, Long> wordCount3 = Arrays.stream(words)
.collect(Collectors.groupingBy(w -> w, Collectors.counting()));
9.3 分组数据
// 场景:按年龄分组学生
List<Student> students = Arrays.asList(
new Student("张三", 20),
new Student("李四", 21),
new Student("王五", 20),
new Student("赵六", 21)
);
// 方式1:使用HashMap
Map<Integer, List<Student>> groupByAge = new HashMap<>();
for (Student student : students) {
groupByAge.computeIfAbsent(student.getAge(), k -> new ArrayList<>())
.add(student);
}
// 结果:{20=[张三, 王五], 21=[李四, 赵六]}
// 方式2:使用Stream
Map<Integer, List<Student>> groupByAge2 = students.stream()
.collect(Collectors.groupingBy(Student::getAge));
9.4 排序和过滤
// 场景:筛选并排序商品
List<Product> products = Arrays.asList(
new Product("手机", 3999),
new Product("电脑", 5999),
new Product("平板", 2999),
new Product("耳机", 299)
);
// 筛选价格大于1000的商品,按价格降序排列
List<Product> filtered = products.stream()
.filter(p -> p.getPrice() > 1000)
.sorted((p1, p2) -> Double.compare(p2.getPrice(), p1.getPrice()))
.collect(Collectors.toList());
// 结果:[电脑(5999), 手机(3999), 平板(2999)]
9.5 数据转换
// 场景:提取用户ID列表
List<User> users = Arrays.asList(
new User(1, "张三"),
new User(2, "李四"),
new User(3, "王五")
);
// 方式1:传统方式
List<Integer> userIds = new ArrayList<>();
for (User user : users) {
userIds.add(user.getId());
}
// 方式2:使用Stream
List<Integer> userIds2 = users.stream()
.map(User::getId)
.collect(Collectors.toList());
// 转换为Map
Map<Integer, String> userMap = users.stream()
.collect(Collectors.toMap(User::getId, User::getName));
// 结果:{1=张三, 2=李四, 3=王五}
9.6 缓存实现
// 场景:实现简单的缓存
public class SimpleCache<K, V> {
private Map<K, V> cache = new HashMap<>();
public V get(K key) {
return cache.get(key);
}
public void put(K key, V value) {
cache.put(key, value);
}
public boolean containsKey(K key) {
return cache.containsKey(key);
}
public void clear() {
cache.clear();
}
}
// 使用缓存
SimpleCache<String, User> userCache = new SimpleCache<>();
userCache.put("user123", new User("张三"));
User user = userCache.get("user123");
9.7 队列和栈
// 场景1:任务队列(先进先出)
Queue<Task> taskQueue = new LinkedList<>();
taskQueue.offer(new Task("任务1"));
taskQueue.offer(new Task("任务2"));
Task task = taskQueue.poll(); // 获取任务1
// 场景2:撤销操作(后进先出)
Deque<Command> undoStack = new LinkedList<>();
undoStack.push(new Command("操作1"));
undoStack.push(new Command("操作2"));
Command lastCommand = undoStack.pop(); // 撤销操作2
// 场景3:优先级队列
PriorityQueue<Task> priorityQueue = new PriorityQueue<>(
(t1, t2) -> Integer.compare(t2.getPriority(), t1.getPriority())
);
priorityQueue.offer(new Task("低优先级", 1));
priorityQueue.offer(new Task("高优先级", 10));
Task highPriorityTask = priorityQueue.poll(); // 获取高优先级任务
十、集合使用的最佳实践
10.1 选择合适的集合类型
// ✓ 正确:根据场景选择
// 需要快速随机访问
List<String> list = new ArrayList<>();
// 需要频繁在头尾插入删除
List<String> deque = new LinkedList<>();
// 需要去重
Set<String> set = new HashSet<>();
// 需要排序
Set<String> sortedSet = new TreeSet<>();
// 需要键值对存储
Map<String, Integer> map = new HashMap<>();
// ✗ 错误:不考虑场景
// 频繁随机访问却使用LinkedList
List<String> badList = new LinkedList<>();
for (int i = 0; i < 10000; i++) {
badList.get(i); // 性能差
}
10.2 指定初始容量
// ✓ 正确:已知大小时指定初始容量
List<String> list = new ArrayList<>(1000);
Map<String, Integer> map = new HashMap<>(1000);
// 避免频繁扩容,提升性能
// ✗ 错误:大量数据使用默认容量
List<String> badList = new ArrayList<>(); // 默认10
for (int i = 0; i < 10000; i++) {
badList.add("item" + i); // 多次扩容,性能差
}
10.3 使用泛型
// ✓ 正确:使用泛型,类型安全
List<String> list = new ArrayList<>();
list.add("Java");
String item = list.get(0); // 不需要强制转换
// ✗ 错误:不使用泛型
List rawList = new ArrayList();
rawList.add("Java");
rawList.add(123); // 可以添加任意类型,不安全
String item = (String) rawList.get(0); // 需要强制转换
10.4 避免在循环中修改集合
// ✗ 错误:在增强for循环中删除元素
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String item : list) {
if (item.equals("B")) {
list.remove(item); // ConcurrentModificationException
}
}
// ✓ 正确:使用Iterator
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (item.equals("B")) {
iterator.remove();
}
}
// ✓ 正确:使用removeIf
list.removeIf(item -> item.equals("B"));
10.5 使用不可变集合
// 场景:常量集合,防止被修改
// 方式1:Collections.unmodifiableXxx
List<String> list = Arrays.asList("A", "B", "C");
List<String> immutableList = Collections.unmodifiableList(list);
// immutableList.add("D"); // UnsupportedOperationException
// 方式2:Java 9+ List.of
List<String> immutableList2 = List.of("A", "B", "C");
Set<String> immutableSet = Set.of("A", "B", "C");
Map<String, Integer> immutableMap = Map.of("A", 1, "B", 2);
// 方式3:Guava ImmutableXxx
ImmutableList<String> guavaList = ImmutableList.of("A", "B", "C");
ImmutableSet<String> guavaSet = ImmutableSet.of("A", "B", "C");
ImmutableMap<String, Integer> guavaMap = ImmutableMap.of("A", 1, "B", 2);
10.6 注意equals和hashCode
// 自定义对象作为HashMap的key或HashSet的元素时
// 必须重写equals和hashCode方法
public class Student {
private String name;
private int age;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
// 使用
Set<Student> students = new HashSet<>();
students.add(new Student("张三", 20));
students.add(new Student("张三", 20)); // 正确去重
10.7 使用Stream API简化代码
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// ✗ 传统方式:代码冗长
List<Integer> result = new ArrayList<>();
for (Integer num : numbers) {
if (num % 2 == 0) {
int square = num * num;
result.add(square);
}
}
// ✓ Stream方式:代码简洁
List<Integer> result2 = numbers.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * n)
.collect(Collectors.toList());
十一、集合API速查表
11.1 List常用方法速查
┌────────────────────────────────────────────────────────────────┐
│ List常用方法 │
├────────────────────────────────────────────────────────────────┤
│ add(E e) 在末尾添加元素 │
│ add(int index, E element) 在指定位置插入元素 │
│ get(int index) 获取指定位置的元素 │
│ set(int index, E element) 替换指定位置的元素 │
│ remove(int index) 删除指定位置的元素 │
│ remove(Object o) 删除指定元素 │
│ size() 获取元素个数 │
│ isEmpty() 判断是否为空 │
│ contains(Object o) 判断是否包含某元素 │
│ indexOf(Object o) 查找元素第一次出现的位置 │
│ clear() 清空集合 │
│ subList(int from, int to) 获取子列表 │
└────────────────────────────────────────────────────────────────┘
11.2 Set常用方法速查
┌────────────────────────────────────────────────────────────────┐
│ Set常用方法 │
├────────────────────────────────────────────────────────────────┤
│ add(E e) 添加元素(重复返回false) │
│ remove(Object o) 删除元素 │
│ contains(Object o) 判断是否包含某元素 │
│ size() 获取元素个数 │
│ isEmpty() 判断是否为空 │
│ clear() 清空集合 │
│ addAll(Collection c) 添加另一个集合的所有元素(并集) │
│ retainAll(Collection c) 保留两个集合的交集 │
│ removeAll(Collection c) 删除另一个集合的所有元素(差集) │
└────────────────────────────────────────────────────────────────┘
11.3 Map常用方法速查
┌────────────────────────────────────────────────────────────────┐
│ Map常用方法 │
├────────────────────────────────────────────────────────────────┤
│ put(K key, V value) 添加/更新键值对 │
│ get(Object key) 获取value │
│ getOrDefault(K key, V def) 获取value,不存在返回默认值 │
│ remove(Object key) 删除键值对 │
│ containsKey(Object key) 判断是否包含key │
│ containsValue(Object value) 判断是否包含value │
│ size() 获取键值对个数 │
│ isEmpty() 判断是否为空 │
│ clear() 清空Map │
│ keySet() 获取所有key的Set │
│ values() 获取所有value的Collection │
│ entrySet() 获取所有键值对的Set │
│ putIfAbsent(K key, V value) key不存在时才添加 │
│ computeIfAbsent(K key, F) key不存在时计算并添加 │
│ merge(K key, V value, F) 合并value │
└────────────────────────────────────────────────────────────────┘
十二、练习题
12.1 基础练习
练习1:List基本操作
// 创建一个ArrayList,添加5个学生姓名
// 要求:
// 1. 在索引2处插入一个新学生
// 2. 删除第一个学生
// 3. 修改最后一个学生的姓名
// 4. 遍历并打印所有学生
练习2:Set去重
// 给定一个包含重复元素的List
List<Integer> numbers = Arrays.asList(1, 2, 3, 2, 4, 3, 5, 1);
// 要求:使用Set去重,并保持原有顺序
练习3:Map统计
// 统计字符串中每个字符出现的次数
String text = "hello world";
// 要求:使用HashMap统计,输出每个字符及其出现次数
12.2 进阶练习
练习4:自定义对象排序
// 创建Student类,包含name和score字段
// 要求:
// 1. 创建一个List存储多个学生
// 2. 按分数降序排序
// 3. 分数相同时按姓名升序排序
练习5:集合转换
// 给定一个List<User>,User包含id和name字段
// 要求:转换为Map<Integer, String>,key为id,value为name
练习6:数据分组
// 给定一个List<Product>,Product包含category和price字段
// 要求:按category分组,计算每个分类的平均价格
12.3 综合练习
练习7:实现LRU缓存
// 要求:
// 1. 继承LinkedHashMap实现LRU缓存
// 2. 指定最大容量
// 3. 超过容量时自动删除最久未使用的元素
// 4. 提供get和put方法
练习8:多线程安全的计数器
// 要求:
// 1. 使用ConcurrentHashMap实现线程安全的计数器
// 2. 提供increment方法增加计数
// 3. 提供getCount方法获取计数
// 4. 多线程环境下测试正确性
十三、学习检查清单
- 理解Java集合框架的整体架构
- 掌握List、Set、Map三大体系的特点
- 熟练使用ArrayList和LinkedList
- 理解ArrayList的扩容机制
- 掌握HashSet的去重原理
- 理解HashMap的底层实现
- 掌握HashMap的put和get过程
- 了解TreeSet和TreeMap的排序机制
- 掌握LinkedHashMap的使用场景
- 理解线程安全集合的实现方式
- 熟练使用Collections工具类
- 掌握集合的5种遍历方式
- 能够根据场景选择合适的集合类型
- 理解集合的性能特点
- 掌握集合使用的最佳实践
- 能够解决集合相关的常见问题
- 熟练使用Stream API操作集合
- 理解ConcurrentHashMap的原理
- 掌握自定义对象的equals和hashCode
- 能够实现常见的集合应用场景
十四、常见面试题
14.1 ArrayList vs LinkedList
Q: ArrayList和LinkedList的区别?
A:
- 底层结构:ArrayList使用动态数组,LinkedList使用双向链表
- 随机访问:ArrayList O(1),LinkedList O(n)
- 插入删除:ArrayList O(n),LinkedList O(1)(已知节点)
- 内存占用:ArrayList连续内存,LinkedList额外存储指针
- 适用场景:ArrayList适合查询多,LinkedList适合增删多
14.2 HashMap原理
Q: HashMap的底层实现原理?
A:
- JDK 1.8使用数组+链表+红黑树
- 通过hash函数计算key的哈希值
- 根据哈希值确定数组索引
- 哈希冲突时使用链表或红黑树
- 链表长度≥8时转换为红黑树
- 负载因子0.75,超过阈值时扩容2倍
14.3 ConcurrentHashMap
Q: ConcurrentHashMap如何实现线程安全?
A:
- JDK 1.7使用分段锁(Segment)
- JDK 1.8使用CAS + synchronized
- 读操作无锁,写操作锁定单个节点
- 提高并发度,性能优于Hashtable
14.4 fail-fast机制
Q: 什么是fail-fast机制?
A:
- 快速失败机制,检测并发修改
- 遍历时如果集合被修改,抛出ConcurrentModificationException
- 通过modCount字段实现
- 解决方案:使用Iterator.remove()或使用并发集合
十五、总结
Java集合框架是Java开发中最常用的API之一,掌握集合框架对于提高开发效率至关重要。
核心要点:
- 三大体系:List(有序可重复)、Set(无序不重复)、Map(键值对)
- 常用实现:ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap
- 性能特点:ArrayList查询快、LinkedList增删快、HashMap查询快
- 线程安全:CopyOnWriteArrayList、ConcurrentHashMap
- 最佳实践:选择合适的集合、指定初始容量、使用泛型、注意并发修改