Java 集合框架详解
Java 集合框架(Collection Framework)是 Java 语言中处理对象集合的核心 API,位于
java.util 包下。它提供了统一的接口规范和丰富的实现类,用于存储、操作和管理一组对象,解决了数组长度固定、类型单一等局限性。本文将从框架架构、核心接口、实现类特性到实战场景,全面解析 Java 集合框架,帮助你理解其设计思想与最佳实践。
一、集合框架整体架构:接口与层次关系
Java 集合框架的核心是接口(定义规范)和实现类(提供具体功能),按数据结构可分为两大体系:Collection(单值集合) 和Map(键值对集合)。整体层次结构如下:
java.util
├─ Collection(单值集合根接口)
│ ├─ List(有序、可重复)
│ │ ├─ ArrayList(动态数组)
│ │ ├─ LinkedList(双向链表)
│ │ └─ Vector(线程安全动态数组,已过时)
│ │ └─ Stack(栈,继承自Vector,已过时)
│ ├─ Set(无序、不可重复)
│ │ ├─ HashSet(哈希表实现)
│ │ ├─ TreeSet(红黑树实现,有序)
│ │ └─ LinkedHashSet(哈希表+链表,维持插入顺序)
│ └─ Queue(队列,FIFO)
│ ├─ LinkedList(双向链表实现的队列)
│ ├─ PriorityQueue(优先级队列,基于堆)
│ └─ Deque(双端队列)
│ └─ ArrayDeque(数组实现的双端队列)
└─ Map(键值对集合根接口)
├─ HashMap(哈希表实现)
├─ TreeMap(红黑树实现,键有序)
├─ LinkedHashMap(哈希表+链表,维持插入/访问顺序)
└─ Hashtable(线程安全哈希表,已过时)
└─ Properties(键值均为字符串的配置类)
核心设计思想:
- 接口标准化:通过
Collection、List、Set、Map等接口定义统一操作(如add()、remove()、iterator()),实现类遵循接口规范,便于替换和扩展。 - 数据结构封装:将数组、链表、红黑树、哈希表等数据结构封装为实现类,开发者无需关注底层细节,直接调用 API 即可。
- 泛型支持:JDK 5+ 引入泛型(如
List<String>),解决集合元素类型不安全问题,避免运行时类型转换错误。
二、Collection 接口:单值集合的通用规范
Collection 是所有单值集合的根接口,定义了操作集合的通用方法,主要包括:| 方法签名 | 功能描述 |
|---|---|
boolean add(E e) |
添加元素到集合 |
boolean remove(Object o) |
从集合中移除指定元素 |
boolean contains(Object o) |
判断集合是否包含指定元素 |
int size() |
返回集合元素个数 |
boolean isEmpty() |
判断集合是否为空 |
Iterator<E> iterator() |
返回迭代器,用于遍历集合 |
Object[] toArray() |
将集合转换为数组 |
注意:Collection接口未提供索引访问方法(如get(int index)),这类方法由子接口List定义。
三、List 接口:有序可重复的动态序列
List 接口继承自 Collection,特点是元素有序(插入顺序)、可重复,支持通过索引访问元素,是最常用的集合类型之一。1. ArrayList:基于动态数组的 List 实现
底层实现:
- 核心是动态扩容数组:初始容量为 10,当元素数量超过当前容量时,自动扩容为原容量的 1.5 倍(
oldCapacity + (oldCapacity >> 1))。 - 存储结构:连续内存空间,元素按索引顺序存储,类似数组但长度可动态调整。
核心特性:
- 时间复杂度:
- 随机访问(
get(int index)):O(1)(直接通过索引定位); - 尾部插入 / 删除(
add(E e)、remove(int size-1)):O(1); - 中间插入 / 删除(
add(int index, E e)):O(n)(需移动后续元素)。
- 随机访问(
- 优缺点:
- 优点:查询效率高,适合读多写少场景;
- 缺点:中间插入 / 删除效率低,扩容时可能浪费内存(需复制旧数组到新数组)。
示例代码:
import java.util.ArrayList;
import java.util.List;
public class ArrayListDemo {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
// 添加元素
fruits.add("苹果");
fruits.add("香蕉");
fruits.add("橙子");
// 索引访问
System.out.println(fruits.get(1)); // 输出:香蕉
// 中间插入
fruits.add(1, "草莓"); // 结果:[苹果, 草莓, 香蕉, 橙子]
// 遍历(for循环+索引)
for (int i = 0; i < fruits.size(); i++) {
System.out.print(fruits.get(i) + " "); // 输出:苹果 草莓 香蕉 橙子
}
// 删除元素
fruits.remove(2); // 删除索引2的"香蕉"
System.out.println("\n" + fruits); // 输出:[苹果, 草莓, 橙子]
}
}
2. LinkedList:基于双向链表的 List 实现
底层实现:
- 核心是双向链表:每个节点(
Node)包含prev(前驱节点引用)、item(元素值)、next(后继节点引用)。 - 存储结构:非连续内存空间,元素通过节点引用关联,无需预先分配固定大小的内存。
核心特性:
- 时间复杂度:
- 随机访问(
get(int index)):O(n)(需从头 / 尾遍历到指定索引); - 头部 / 尾部插入 / 删除(
addFirst(E e)、removeLast()):O(1); - 中间插入 / 删除(已知节点位置时):
O(1)(仅需修改节点引用),但定位节点仍需O(n)。
- 随机访问(
- 优缺点:
- 优点:头尾操作效率高,内存利用率灵活(无需连续空间);
- 缺点:查询效率低,不适合频繁随机访问。
示例代码:
import java.util.LinkedList;
import java.util.List;
public class LinkedListDemo {
public static void main(String[] args) {
List<Integer> numbers = new LinkedList<>();
// 添加元素
numbers.add(10);
numbers.add(20);
numbers.add(30);
// 头部插入
numbers.add(0, 5); // 结果:[5, 10, 20, 30]
// 尾部删除
numbers.remove(numbers.size() - 1); // 结果:[5, 10, 20]
// 遍历(增强for循环)
for (int num : numbers) {
System.out.print(num + " "); // 输出:5 10 20
}
}
}
3. List 实现类对比与选择
| 场景需求 | 推荐实现类 | 理由 |
|---|---|---|
| 频繁查询,少量增删 | ArrayList |
随机访问效率 O(1),适合读多写少 |
| 频繁头尾增删,少量查询 | LinkedList |
头尾操作 O(1),适合队列 / 栈场景 |
| 多线程环境(需线程安全) | CopyOnWriteArrayList |
读写分离,适合读多写极少场景(JUC 包) |
四、Set 接口:无序不可重复的集合
Set 接口继承自 Collection,特点是元素无序(插入顺序不保证)、不可重复,适合存储唯一值(如用户 ID、标签)。其核心是去重机制:通过元素的 hashCode() 和 equals() 方法判断是否重复(先比较哈希值,哈希值相同再比较内容)。1. HashSet:基于哈希表的 Set 实现
底层实现:
- 本质是HashMap 的封装:
HashSet内部维护一个HashMap,元素作为HashMap的key存储(value为固定的空对象PRESENT)。 - 去重逻辑:同
HashMap的key去重,依赖元素重写hashCode()和equals()方法。
核心特性:
- 时间复杂度:添加 / 删除 / 查询均为
O(1)(哈希表理想情况,无哈希冲突); - 无序性:元素存储位置由哈希值决定,遍历顺序与插入顺序无关;
- 允许 null 元素:但仅能有一个
null(因不可重复)。
示例代码:
import java.util.HashSet;
import java.util.Set;
public class HashSetDemo {
public static void main(String[] args) {
Set<String> cities = new HashSet<>();
// 添加元素
cities.add("北京");
cities.add("上海");
cities.add("广州");
cities.add("北京"); // 重复元素,不添加
// 遍历(无序)
for (String city : cities) {
System.out.print(city + " "); // 输出可能为:上海 北京 广州(顺序不固定)
}
// 判断是否包含
System.out.println("\n是否包含深圳?" + cities.contains("深圳")); // 输出:false
}
}
2. TreeSet:基于红黑树的有序 Set 实现
底层实现:
- 本质是TreeMap 的封装:内部维护一个
TreeMap,元素作为TreeMap的key存储,依赖红黑树(自平衡二叉查找树)实现排序。 - 排序逻辑:
- 自然排序:元素实现
Comparable接口,重写compareTo()方法; - 定制排序:创建
TreeSet时传入Comparator比较器。
- 自然排序:元素实现
核心特性:
- 时间复杂度:添加 / 删除 / 查询均为
O(log n)(红黑树高度为log n); - 有序性:元素按排序规则升序排列(默认自然排序);
- 不允许 null 元素:插入
null会抛NullPointerException。
示例代码:
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
public class TreeSetDemo {
public static void main(String[] args) {
// 自然排序(整数按大小排序)
Set<Integer> nums = new TreeSet<>();
nums.add(30);
nums.add(10);
nums.add(20);
System.out.println(nums); // 输出:[10, 20, 30](自然排序)
// 定制排序(字符串按长度降序)
Set<String> words = new TreeSet<>(Comparator.comparingInt(String::length).reversed());
words.add("apple");
words.add("banana");
words.add("pear");
System.out.println(words); // 输出:[banana, apple, pear](长度:6→5→4)
}
}
3. LinkedHashSet:维持插入顺序的 Set 实现
底层实现:
- 继承自
HashSet,内部使用LinkedHashMap(哈希表 + 双向链表),通过链表记录元素插入顺序。
核心特性:
- 兼具
HashSet的查询效率(O(1))和LinkedList的顺序性(插入顺序); - 内存开销略高于
HashSet(需维护链表)。
适用场景:
需去重且关注插入顺序的场景(如日志记录、历史操作记录)。
4. Set 实现类对比与选择
| 场景需求 | 推荐实现类 | 理由 |
|---|---|---|
| 去重且无需排序 | HashSet |
效率最高,O(1) 操作复杂度 |
| 去重且需排序 | TreeSet |
自动按规则排序,O(log n) 效率 |
| 去重且需保留插入顺序 | LinkedHashSet |
平衡效率与顺序需求,内存略高 |
五、Queue 接口:先进先出的队列
Queue 接口继承自 Collection,定义了FIFO(先进先出) 的队列操作规范,主要用于实现队列、缓冲区等场景。1. LinkedList:双向链表实现的队列
LinkedList 实现了 Queue 接口,可作为队列使用,核心方法:| 方法 | 功能 | 失败时行为 |
|---|---|---|
boolean offer(E e) |
入队(尾部添加) | 失败返回 false |
E poll() |
出队(头部移除并返回) | 队空返回 null |
E peek() |
查看队头元素 | 队空返回 null |
示例代码:
import java.util.LinkedList;
import java.util.Queue;
public class QueueDemo {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
// 入队
queue.offer("任务1");
queue.offer("任务2");
queue.offer("任务3");
// 出队(FIFO)
while (!queue.isEmpty()) {
System.out.println("处理:" + queue.poll());
// 输出:处理:任务1 → 处理:任务2 → 处理:任务3
}
}
}
2. PriorityQueue:基于堆的优先级队列
底层实现:
- 基于小顶堆(默认)实现,元素按优先级出队(优先级最高的元素先出队)。
- 排序逻辑:同
TreeSet,支持自然排序或定制排序。
核心特性:
- 出队顺序由优先级决定,而非插入顺序;
- 时间复杂度:入队 / 出队
O(log n),查看队头O(1)。
示例代码:
import java.util.PriorityQueue;
import java.util.Queue;
public class PriorityQueueDemo {
public static void main(String[] args) {
// 优先级队列(默认小顶堆,整数从小到大出队)
Queue<Integer> pq = new PriorityQueue<>();
pq.offer(30);
pq.offer(10);
pq.offer(20);
while (!pq.isEmpty()) {
System.out.println("出队:" + pq.poll());
// 输出:出队:10 → 出队:20 → 出队:30(按优先级)
}
}
}
六、Map 接口:键值对映射集合
Map 接口是独立于 Collection 的另一大体系,用于存储键值对(key-value),其中 key 唯一(不可重复),value 可重复。key 的去重逻辑同 Set(依赖 hashCode() 和 equals())。1. HashMap:基于哈希表的 Map 实现
底层实现(JDK 1.8+):
- 数组 + 链表 + 红黑树:
- 数组(桶):每个索引对应一个链表或红黑树;
- 链表:当哈希冲突时,元素以链表形式存储;
- 红黑树:当链表长度超过 8 且数组容量 ≥ 64 时,链表转为红黑树(优化查询效率)。
- 扩容机制:初始容量 16,负载因子 0.75(当元素数 > 容量 × 负载因子时,扩容为原容量的 2 倍)。
核心特性:
- 时间复杂度:理想情况
O(1)(哈希表无冲突),红黑树场景O(log n); - 无序性:
key的存储顺序与插入顺序无关; - 允许 null:
key最多一个null,value可多个null; - 线程不安全:多线程并发修改可能导致死循环(JDK 1.7)或数据异常。
示例代码:
import java.util.HashMap;
import java.util.Map;
public class HashMapDemo {
public static void main(String[] args) {
Map<String, Integer> scores = new HashMap<>();
// 添加键值对
scores.put("张三", 90);
scores.put("李四", 85);
scores.put("王五", 95);
// 获取值
System.out.println("李四的成绩:" + scores.get("李四")); // 输出:85
// 遍历(entrySet() 效率高于 keySet())
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
// 判断key是否存在
System.out.println("是否有赵六?" + scores.containsKey("赵六")); // 输出:false
}
}
2. TreeMap:基于红黑树的有序 Map 实现
底层实现:
- 基于红黑树,
key按排序规则(自然排序或定制排序)存储,遍历顺序为升序。
核心特性:
- 时间复杂度:添加 / 删除 / 查询均为
O(log n); - 有序性:
key按排序规则排列,支持范围查询(如subMap()、headMap()); - 不允许 null key:
key为null会抛NullPointerException。
示例代码:
import java.util.Map;
import java.util.TreeMap;
public class TreeMapDemo {
public static void main(String[] args) {
// 自然排序(字符串按字典序)
Map<String, String> dict = new TreeMap<>();
dict.put("banana", "香蕉");
dict.put("apple", "苹果");
dict.put("cherry", "樱桃");
// 遍历(按key升序)
for (Map.Entry<String, String> entry : dict.entrySet()) {
System.out.println(entry.getKey() + " → " + entry.getValue());
// 输出:apple → 苹果 → banana → 香蕉 → cherry → 樱桃
}
}
}
3. LinkedHashMap:维持顺序的 Map 实现
底层实现:
- 继承自
HashMap,内部通过双向链表记录key的顺序,支持两种顺序:- 插入顺序(默认):遍历顺序与插入顺序一致;
- 访问顺序(
accessOrder=true):每次访问(get())元素后,该元素移至链表尾部,适合实现 LRU 缓存。
示例代码(LRU 缓存思想):
import java.util.LinkedHashMap;
import java.util.Map;
public class LinkedHashMapDemo {
public static void main(String[] args) {
// 访问顺序模式(accessOrder=true),初始容量16,负载因子0.75
Map<String, Integer> lruCache = new LinkedHashMap<>(16, 0.75f, true) {
// 重写此方法,当元素数超过3时,自动删除最久未访问的元素
@Override
protected boolean removeEldestEntry(Map.Entry<String, Integer> eldest) {
return size() > 3;
}
};
lruCache.put("A", 1);
lruCache.put("B", 2);
lruCache.put("C", 3);
System.out.println(lruCache.keySet()); // 输出:[A, B, C](插入顺序)
lruCache.get("A"); // 访问A,移至尾部
System.out.println(lruCache.keySet()); // 输出:[B, C, A]
lruCache.put("D", 4); // 超过容量3,删除最久未访问的B
System.out.println(lruCache.keySet()); // 输出:[C, A, D]
}
}
4. Map 实现类对比与选择
| 场景需求 | 推荐实现类 | 理由 |
|---|---|---|
| 键值对存储,无序 | HashMap |
效率最高,适合多数场景 |
| 键值对存储,需按 key 排序 | TreeMap |
支持范围查询,O(log n) 效率 |
| 键值对存储,需保留插入 / 访问顺序 | LinkedHashMap |
可实现 LRU 缓存,内存略高 |
| 多线程环境(需线程安全) | ConcurrentHashMap |
分段锁 / CAS 实现,高并发下性能优于 Hashtable(JUC 包) |
七、集合框架的关键工具类
1. Collections:集合工具类
提供静态方法操作集合,如排序、查找、同步包装等:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CollectionsDemo {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(3);
list.add(1);
list.add(2);
// 排序(自然排序)
Collections.sort(list);
System.out.println(list); // 输出:[1, 2, 3]
// 反转
Collections.reverse(list);
System.out.println(list); // 输出:[3, 2, 1]
// 创建线程安全的List(不推荐,效率低,建议用JUC包的并发集合)
List<Integer> syncList = Collections.synchronizedList(list);
}
}
2. Arrays:数组工具类
提供数组与集合的转换方法(如
asList()):import java.util.Arrays;
import java.util.List;
public class ArraysDemo {
public static void main(String[] args) {
// 数组转List(返回的是固定大小的List,不可添加/删除元素)
List<String> list = Arrays.asList("a", "b", "c");
System.out.println(list); // 输出:[a, b, c]
// 若需可修改的List,需包装为ArrayList
List<String> mutableList = new ArrayList<>(Arrays.asList("a", "b", "c"));
mutableList.add("d"); // 可行
}
}
八、集合框架的线程安全性
Java 集合框架中,
ArrayList、HashMap、HashSet 等均为线程不安全(多线程并发修改可能导致数据异常),线程安全的集合解决方案:-
使用同步包装类:
Collections.synchronizedList()、synchronizedMap()等,通过加锁实现线程安全,但性能低(全局锁)。 -
使用 JUC 并发集合:
java.util.concurrent包下的集合(如CopyOnWriteArrayList、ConcurrentHashMap),采用更高效的并发策略(如读写分离、分段锁),适合高并发场景。 -
手动同步:在多线程操作时,通过
synchronized或Lock手动加锁,灵活但需注意锁粒度。
九、总结:集合框架的选择指南
Java 集合框架的核心是 “根据场景选结构”,选择时需关注:
- 操作类型:查询多→
ArrayList/HashMap;增删多(头尾)→LinkedList;需排序→TreeSet/TreeMap。 - 顺序需求:需插入顺序→
LinkedList/LinkedHashSet/LinkedHashMap;需排序→Tree系列。 - 线程安全:单线程→普通集合;多线程→JUC 并发集合(如
ConcurrentHashMap)。 - 性能考量:哈希表(
Hash系列)平均O(1)效率最高;红黑树(Tree系列)O(log n)适合排序场景。
浙公网安备 33010602011771号