Java 集合框架
Java 集合框架:List、Set、Map 全家桶详解
学习日期:2026-06-16 难度:⭐⭐⭐⭐ 核心
前言
Java 集合可以说是日常开发中用得最多的 API,没有之一。存数据、遍历、查找、排序……处处都有它的身影。
但集合家族成员众多,初学者很容易懵:ArrayList 还是 LinkedList?HashMap 为什么线程不安全?HashSet 和 HashMap 是什么关系?
这篇把 Java 集合框架全貌理清楚,每个常用集合的特点、底层原理、使用场景一次说透。
一、集合框架总览
Java 集合主要分两大阵营:Collection 和 Map。
Iterable
│
Collection
│
┌─────────────┼─────────────┐
│ │ │
List Set Queue
│ │ │
┌──────┴──────┐ ┌──┴────┐ ┌───┴──────┐
│ │ │ │ │ │ │
ArrayList LinkedList HashSet TreeSet ArrayDeque PriorityQueue
| | |
| | └─ LinkedHashSet
| |
Vector Stack(已废弃)
Map
│
┌─────────────┼─────────────┐
│ │ │
HashMap TreeMap Hashtable(老)
│ │
└─ LinkedHashMap
|
└─ ConcurrentHashMap(并发)
核心区别:
表格
| 接口 | 特点 | 元素顺序 | 允许重复 | 允许 null |
|---|---|---|---|---|
| List | 有序列表 | 按插入顺序 | ✅ | ✅ |
| Set | 不重复集合 | 取决于实现 | ❌ | 取决于实现 |
| Queue | 队列 | FIFO / 优先级 | ✅ | 取决于实现 |
| Map | 键值对 | 取决于实现 | Key 不重复 | 取决于实现 |
二、List 家族
2.1 ArrayList——动态数组(最常用)
底层原理:Object 数组,默认初始容量 10,不够时扩容 1.5 倍。
// 创建
List<String> list = new ArrayList<>();
List<String> list2 = new ArrayList<>(100); // 指定初始容量(推荐)
// 增删改查
list.add("Java"); // 末尾添加
list.add(0, "Python"); // 指定位置插入
list.get(0); // 获取元素
list.set(0, "Go"); // 修改元素
list.remove(0); // 按索引删
list.remove("Java"); // 按元素删(删第一个匹配的)
// 其他常用
list.size(); // 大小
list.isEmpty(); // 是否为空
list.contains("Java"); // 是否包含
list.indexOf("Java"); // 首次出现位置
list.lastIndexOf("Java"); // 最后出现位置
list.clear(); // 清空
遍历方式:
// 1. for-i 循环(可以操作索引)
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// 2. 增强 for(最常用)
for (String s : list) {
System.out.println(s);
}
// 3. Iterator 迭代器(遍历时删除元素要用它!)
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("要删除")) {
it.remove(); // 安全删除
}
}
// 4. Lambda forEach(Java 8+)
list.forEach(s -> System.out.println(s));
list.forEach(System.out::println); // 方法引用简化
⚠️ 重要坑点:增强 for 循环中不能直接 add/remove 元素,会抛
ConcurrentModificationException。要删除用 Iterator。
特点总结:
- ✅ 查询快(O(1) 随机访问)
- ❌ 中间插入删除慢(需要移动元素)
- ✅ 适合读多写少的场景
2.2 LinkedList——双向链表
底层原理:双向链表,每个节点存元素 + 前后指针。
LinkedList<String> linked = new LinkedList<>();
// List 接口的方法都有,额外还有链表特有方法
linked.addFirst("头"); // 头部添加
linked.addLast("尾"); // 尾部添加
linked.getFirst(); // 获取头
linked.getLast(); // 获取尾
linked.removeFirst(); // 删除头
linked.removeLast(); // 删除尾
// 还能当队列、栈用
linked.offer("入队");
linked.poll(); // 出队
linked.peek(); // 看队头
特点总结:
- ✅ 头尾插入删除快(O(1))
- ❌ 查询慢(O(n),得从头找)
- ✅ 实现了 Deque 接口,可当队列/栈用
- ❌ 实际开发中用得不多,性能优势不明显
💡 面试常问:ArrayList 和 LinkedList 的区别?
- 底层:数组 vs 双向链表
- 访问:ArrayList 随机访问 O(1),LinkedList O(n)
- 增删:ArrayList 中间增删 O(n),LinkedList 头尾 O(1)、中间 O(n)
- 内存:ArrayList 连续内存,LinkedList 节点分散
2.3 Vector 和 Stack(了解就行,别用)
- Vector:ArrayList 的线程安全版,所有方法都加 synchronized,性能差,已废弃
- Stack:继承 Vector,实现栈,设计有问题,推荐用
ArrayDeque代替栈
三、Set 家族
3.1 HashSet——哈希表实现
底层原理:其实就是 HashMap 的 Key 部分,值是一个固定的 Object。
Set<String> set = new HashSet<>();
set.add("苹果");
set.add("香蕉");
set.add("苹果"); // 不会报错,但加不进去(重复元素返回 false)
set.size(); // 2
set.contains("苹果"); // true
set.remove("苹果");
// 遍历(没有 get 方法,不能按索引取)
for (String s : set) {
System.out.println(s);
}
set.forEach(System.out::println);
HashSet 如何判断元素重复?
先看 hashCode() 是否相等,再看 equals() 是否为 true。两者都相等才认为是同一个元素。
⚠️ 重点:自定义类要放入 HashSet,必须重写
hashCode()和equals()!class Person {
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;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
特点总结:
- ✅ 增删查都是 O(1),速度快
- ❌ 元素无序(不保证顺序)
- ❌ 元素不可重复
- ❌ 线程不安全
3.2 LinkedHashSet——有序不重复
底层原理:继承 HashSet,底层是 LinkedHashMap,多了一个双向链表维护插入顺序。
Set<String> set = new LinkedHashSet<>();
set.add("A");
set.add("C");
set.add("B");
// 遍历顺序 = 插入顺序:A C B
set.forEach(System.out::println);
特点:
- ✅ 插入有序
- ✅ 性能略低于 HashSet(多维护了链表)
- ✅ 去重又要保持顺序时用
3.3 TreeSet——排序不重复
底层原理:红黑树实现,自动按元素自然排序。
TreeSet<Integer> set = new TreeSet<>();
set.add(5);
set.add(2);
set.add(8);
set.add(1);
// 遍历自动升序:1 2 5 8
set.forEach(System.out::println);
// 额外方法
set.first(); // 最小元素
set.last(); // 最大元素
set.lower(5); // 小于5的最大元素 → 2
set.higher(5); // 大于5的最小元素 → 8
set.headSet(5); // 小于5的子集
set.tailSet(5); // 大于等于5的子集
两种排序方式:
// 方式一:元素实现 Comparable 接口(自然排序)
class Student implements Comparable<Student> {
private int score;
@Override
public int compareTo(Student o) {
// return 负数 放左边,正数 放右边,0 不存
return this.score - o.score; // 按分数升序
}
}
// 方式二:构造时传 Comparator(比较器排序,更灵活)
TreeSet<Student> set = new TreeSet<>((s1, s2) -> {
// 先按分数升序,同分按姓名
int cmp = s1.getScore() - s2.getScore();
return cmp != 0 ? cmp : s1.getName().compareTo(s2.getName());
});
⚠️ TreeSet 判断元素相等的依据是
compareTo返回 0,不是 equals!返回 0 的两个元素会被认为重复,第二个加不进去。
特点总结:
- ✅ 元素自动排序
- ✅ 有丰富的范围查询方法
- ❌ 增删查都是 O(log n),比 HashSet 慢一点
- ❌ 自定义类必须实现排序规则
四、Map 家族
4.1 HashMap——哈希表(最常用)
底层原理:数组 + 链表 + 红黑树(JDK 1.8+)。默认容量 16,负载因子 0.75,超过就扩容 2 倍。
// 创建
Map<String, Integer> map = new HashMap<>();
// 增删改查
map.put("Java", 90); // 添加/修改
map.put("Python", 85);
map.put("Go", 95);
map.get("Java"); // 90(获取)
map.getOrDefault("C++", 0); // 没有则返回默认值
map.containsKey("Java"); // true
map.containsValue(90); // true
map.remove("Java"); // 删除
map.size(); // 大小
map.isEmpty();
map.clear();
// 遍历 key
for (String key : map.keySet()) {
System.out.println(key + " → " + map.get(key));
}
// 遍历 entry(推荐,性能好)
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " → " + entry.getValue());
}
// Lambda 遍历(最优雅)
map.forEach((k, v) -> System.out.println(k + " → " + v));
HashMap 底层原理(简化版) :
- 调用 key 的
hashCode()得到哈希值 - 通过哈希算法计算数组下标(
(n - 1) & hash) - 如果该位置没有元素,直接放进去
- 如果有元素(哈希冲突),用 equals 比较
- 相等:覆盖旧值
- 不等:链表挂在后面(链表长度 ≥ 8 且数组长度 ≥ 64 时转红黑树)
💡 面试高频:HashMap 的扩容机制?
- 默认容量 16,负载因子 0.75
- 当 size > 容量 × 0.75 时,扩容为原来的 2 倍
- 扩容需要重新计算所有元素的位置,性能消耗大
- 预估数据量时建议初始化容量
new HashMap<>(initialCapacity)
特点总结:
- ✅ 增删查都是 O(1),速度快
- ❌ Key 无序
- ❌ 线程不安全
- ✅ Key 可以有一个 null,Value 可以多个 null
4.2 LinkedHashMap——有序的 HashMap
继承 HashMap,多了一个双向链表维护插入顺序或访问顺序。
// 插入有序(默认)
Map<String, Integer> map = new LinkedHashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
// 遍历顺序:a→1, b→2, c→3
// 访问有序(LRU 缓存常用)
// accessOrder = true 时,最近访问的元素移到最后
Map<String, Integer> accessOrderMap =
new LinkedHashMap<>(16, 0.75f, true);
4.3 TreeMap——排序的 Map
底层红黑树,Key 自动排序。
TreeMap<String, Integer> map = new TreeMap<>();
map.put("banana", 2);
map.put("apple", 5);
map.put("cherry", 1);
// 遍历按 key 自然排序:apple→5, banana→2, cherry→1
map.forEach((k, v) -> System.out.println(k + " → " + v));
// 额外方法
map.firstKey(); // 最小的key
map.lastKey(); // 最大的key
map.lowerKey("cherry"); // 小于 cherry 的最大key
map.higherKey("apple"); // 大于 apple 的最小key
map.headMap("cherry"); // 小于 cherry 的子map
同样支持自定义 Comparator,用法和 TreeSet 类似。
4.4 Hashtable 和 ConcurrentHashMap
表格
| 特性 | Hashtable | HashMap | ConcurrentHashMap |
|---|---|---|---|
| 线程安全 | ✅ | ❌ | ✅ |
| 效率 | 低(全表锁) | 高 | 高(分段锁/CAS) |
| 允许 null key/value | ❌ | ✅ | ❌ |
| 状态 | 废弃 | 常用 | 并发场景用 |
💡 并发场景用
ConcurrentHashMap,别用 Hashtable。
五、Queue 队列
5.1 ArrayDeque——双端队列
基于数组实现的双端队列,当栈用比 Stack 好,当队列用比 LinkedList 好。
Deque<String> deque = new ArrayDeque<>();
// 当栈用(LIFO)
deque.push("A"); // 压栈
deque.push("B");
deque.pop(); // 弹栈 → B
deque.peek(); // 看栈顶 → A
// 当队列用(FIFO)
deque.offer("C"); // 入队
deque.poll(); // 出队 → A
deque.peek(); // 看队头
5.2 PriorityQueue——优先队列
底层是堆,元素按优先级出队。
// 默认小顶堆
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(5);
pq.offer(2);
pq.offer(8);
pq.offer(1);
pq.poll(); // 1(最小的先出来)
pq.poll(); // 2
pq.poll(); // 5
// 自定义比较器(大顶堆)
PriorityQueue<Integer> maxHeap =
new PriorityQueue<>((a, b) -> b - a);
六、Collections 工具类
集合操作的工具类,很多实用方法:
List<String> list = new ArrayList<>(Arrays.asList("c", "a", "b"));
// 排序
Collections.sort(list); // [a, b, c]
Collections.sort(list, Collections.reverseOrder()); // [c, b, a]
Collections.shuffle(list); // 随机打乱
// 最大最小
Collections.max(list); // c
Collections.min(list); // a
// 查找(前提是已排序)
Collections.binarySearch(list, "b"); // 1
// 填充
Collections.fill(list, "x"); // 全替换成 x
// 不可变集合
List<String> unmodifiable = Collections.unmodifiableList(list);
// unmodifiable.add("d"); // 抛异常,不可修改
// 单元素/空集合
List<String> singleton = Collections.singletonList("single");
List<String> empty = Collections.emptyList();
七、集合选择指南
表格
| 需求 | 推荐 | 说明 |
|---|---|---|
| 列表,查询多 | ArrayList | 最常用,默认选它 |
| 列表,头尾插删多 | LinkedList / ArrayDeque | 优先 ArrayDeque |
| 去重,不关心顺序 | HashSet | 性能最好 |
| 去重,要插入顺序 | LinkedHashSet | |
| 去重,要排序 | TreeSet | 红黑树,O(log n) |
| 键值对,不关心顺序 | HashMap | 最常用 |
| 键值对,要插入顺序 | LinkedHashMap | |
| 键值对,要key排序 | TreeMap | |
| 并发场景的Map | ConcurrentHashMap | 别用 Hashtable |
| 栈 | ArrayDeque | 比 Stack 好 |
| 队列 | ArrayDeque / LinkedList | |
| 优先级队列 | PriorityQueue | 堆实现 |
八、实战练习
练习1:统计字符串中每个字符出现的次数
public class CharCount {
public static void main(String[] args) {
String str = "hello world hello java";
Map<Character, Integer> countMap = new HashMap<>();
for (char c : str.toCharArray()) {
if (c == ' ') continue;
// 方式一:判断式
// if (countMap.containsKey(c)) {
// countMap.put(c, countMap.get(c) + 1);
// } else {
// countMap.put(c, 1);
// }
// 方式二:getOrDefault
// countMap.put(c, countMap.getOrDefault(c, 0) + 1);
// 方式三:merge(最优雅)
countMap.merge(c, 1, Integer::sum);
}
countMap.forEach((k, v) -> System.out.println(k + " 出现 " + v + " 次"));
}
}
练习2:List 去重(保持顺序)
public class ListDeduplicate {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>(
Arrays.asList(1, 2, 3, 2, 1, 4, 5, 3));
// 方式一:LinkedHashSet(最简单,推荐)
List<Integer> result1 = new ArrayList<>(new LinkedHashSet<>(list));
System.out.println(result1); // [1, 2, 3, 4, 5]
// 方式二:Stream 去重(Java 8+)
List<Integer> result2 = list.stream()
.distinct()
.toList();
System.out.println(result2);
}
}
练习3:学生成绩排序
class Student {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() { return name; }
public int getScore() { return score; }
@Override
public String toString() {
return name + "(" + score + ")";
}
}
public class StudentSort {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("张三", 85));
students.add(new Student("李四", 92));
students.add(new Student("王五", 78));
students.add(new Student("赵六", 92));
students.add(new Student("钱七", 85));
// 按分数降序,同分按姓名字母序
students.sort((s1, s2) -> {
int scoreCompare = s2.getScore() - s1.getScore();
if (scoreCompare != 0) return scoreCompare;
return s1.getName().compareTo(s2.getName());
});
students.forEach(System.out::println);
// 李四(92)
// 赵六(92)
// 张三(85)
// 钱七(85)
// 王五(78)
}
}
练习4:简易 LRU 缓存(LinkedHashMap 版)
class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int capacity;
public LRUCache(int capacity) {
// accessOrder = true 表示按访问顺序排序
super(capacity, 0.75f, true);
this.capacity = capacity;
}
// 超过容量时删除最老的元素
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacity;
}
public static void main(String[] args) {
LRUCache<String, Integer> cache = new LRUCache<>(3);
cache.put("a", 1);
cache.put("b", 2);
cache.put("c", 3);
System.out.println(cache); // {a=1, b=2, c=3}
cache.get("a"); // 访问 a,a 变最新
cache.put("d", 4); // 超过容量,删最老的 b
System.out.println(cache); // {c=3, a=1, d=4}
}
}
九、总结速查表
核心实现对比
表格
| 集合 | 底层 | 有序 | 重复 | null | 增删查 | 线程安全 |
|---|---|---|---|---|---|---|
| ArrayList | 数组 | 插入序 | ✅ | ✅ | 查O(1),增删O(n) | ❌ |
| LinkedList | 双向链表 | 插入序 | ✅ | ✅ | 头尾O(1),查O(n) | ❌ |
| HashSet | HashMap | ❌ | ❌ | 一个null | O(1) | ❌ |
| LinkedHashSet | LinkedHashMap | 插入序 | ❌ | ✅ | O(1) | ❌ |
| TreeSet | 红黑树 | 排序 | ❌ | ❌ | O(log n) | ❌ |
| HashMap | 数组+链表+红黑树 | ❌ | Key不重复 | 一key多value | O(1) | ❌ |
| LinkedHashMap | HashMap+链表 | 插入/访问序 | Key不重复 | ✅ | O(1) | ❌ |
| TreeMap | 红黑树 | Key排序 | Key不重复 | ❌ | O(log n) | ❌ |
| ArrayDeque | 循环数组 | FIFO/LIFO | ✅ | ❌ | O(1) | ❌ |
| PriorityQueue | 堆 | 优先级 | ✅ | ❌ | 入队O(log n) | ❌ |
| ConcurrentHashMap | 分段哈希 | ❌ | Key不重复 | ❌ | O(1) | ✅ |
经验法则
- 默认用 ArrayList 和 HashMap,大多数场景它们是最优解
- Set 元素一定要重写 hashCode 和 equals,TreeSet/TreeMap 要重写比较方法
- 遍历中删除元素用 Iterator 或
removeIf(),别在增强 for 里删 - 预估集合大小时指定初始容量,避免频繁扩容
- 并发场景用 ConcurrentHashMap,别用 Hashtable
- 返回空集合用空集合对象(
Collections.emptyList()),别返回 null
集合框架是 Java 程序员的基本功,面试必考、工作必用。这篇把主流集合的特点、原理和用法都梳理了一遍,但更重要的还是多写多用。熟练掌握集合,代码质量和开发效率都会上一个台阶。🔥
更多推荐
所有评论(0)