Java 集合框架是 Java 语言中最重要的基础库之一,它为开发者提供了一套完善的接口和实现,用于存储、检索、遍历和操作成组数据。作为一名 Java 开发者,深入理解集合框架的设计原理和使用技巧,能够显著提升代码质量和性能表现。
在日常开发中,我们经常需要处理各种数据集合。比如:
如果没有统一的集合框架,开发者就需要自己实现这些数据结构,不仅效率低下,而且容易出错。Java 集合框架通过提供标准化的接口和实现,解决了这些问题。
Java 集合框架主要分为两大体系:
Collection 体系:存储单个元素的容器
Map 体系:存储键值对的容器
| 接口 | 描述 | 常用实现类 |
|---|---|---|
| List | 有序集合,允许重复 | ArrayList, LinkedList, Vector |
| Set | 不重复集合 | HashSet, TreeSet, LinkedHashSet |
| Queue | 队列,FIFO | LinkedList, PriorityQueue, ArrayDeque |
| Map | 键值对映射 | HashMap, TreeMap, LinkedHashMap |
ArrayList 是基于动态数组的实现,其内部维护了一个 Object[] 数组来存储元素。当数组容量不足时,会自动进行扩容(通常扩容为原来的1.5倍)。
java复制// 典型初始化方式
List<String> list = new ArrayList<>(100); // 指定初始容量
提示:在已知元素数量时,建议指定初始容量以避免频繁扩容带来的性能损耗。
LinkedList 是基于双向链表的实现,每个节点(Node)包含前驱、后继指针和元素值:
java复制private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
// ...
}
Vector 是线程安全的 ArrayList,但性能较差(方法使用 synchronized 修饰)。在现代 Java 开发中,通常建议:
HashSet 是基于 HashMap 实现的,元素作为 HashMap 的 key 存储,value 使用一个固定的 Object 对象。
java复制// HashSet 的简化实现
public class HashSet<E> {
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
// ...
}
LinkedHashSet 继承自 HashSet,但维护了一个双向链表来记录插入顺序,因此:
TreeSet 是基于 TreeMap 实现的,使用红黑树数据结构存储元素。
在 JDK 8 中,HashMap 做了重要优化:
java复制// 预估元素数量时指定初始容量
Map<String, Integer> map = new HashMap<>(100);
// 使用 compute 方法进行复杂操作
map.compute("key", (k, v) -> v == null ? 1 : v + 1);
// 使用 merge 方法合并值
map.merge("key", 1, Integer::sum);
LinkedHashMap 继承自 HashMap,但通过维护一个双向链表来保持遍历顺序:
java复制// 简易 LRU 缓存实现
Map<K,V> lruCache = new LinkedHashMap<K,V>(16, 0.75f, true) {
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return size() > maxSize;
}
};
TreeMap 基于红黑树实现,具有以下特点:
java复制// 获取键在 "A"(包含)到 "C"(不包含)之间的子映射
SortedMap<String, Integer> subMap = treeMap.subMap("A", "C");
| 遍历方式 | 适用场景 | 备注 |
|---|---|---|
| for-each | 简单遍历 | 底层使用 Iterator |
| Iterator | 需要删除元素时 | 唯一安全的遍历时删除方式 |
| forEach() | Java 8+ | 内部迭代,可并行 |
| Stream API | 复杂数据处理 | 链式操作,函数式风格 |
错误方式(会抛出 ConcurrentModificationException):
java复制for (String item : list) {
if (shouldRemove(item)) {
list.remove(item); // 错误!
}
}
正确方式:
java复制Iterator<String> it = list.iterator();
while (it.hasNext()) {
if (shouldRemove(it.next())) {
it.remove(); // 正确
}
}
Java 8+ 更简洁的方式:
java复制list.removeIf(item -> shouldRemove(item));
大多数集合(如 ArrayList、HashMap)实现了 Fail-Fast 迭代器,当检测到并发修改时会抛出 ConcurrentModificationException。这是通过维护一个 modCount(修改计数器)实现的。
对于实现了 Comparable 接口的类,可以直接排序:
java复制List<String> list = Arrays.asList("banana", "apple", "pear");
Collections.sort(list); // 自然排序
使用 Comparator 可以实现灵活排序:
java复制// 按字符串长度排序
list.sort(Comparator.comparingInt(String::length));
// 多级排序:先按长度,再按字母顺序
list.sort(Comparator.comparingInt(String::length)
.thenComparing(Comparator.naturalOrder()));
对于自定义对象,可以实现 Comparable 或提供 Comparator:
java复制class Person implements Comparable<Person> {
private String name;
private int age;
@Override
public int compareTo(Person other) {
return Integer.compare(this.age, other.age);
}
// ...
}
// 使用
List<Person> people = ...;
Collections.sort(people);
// 或者使用 Comparator
people.sort(Comparator.comparing(Person::getName)
.thenComparingInt(Person::getAge));
Stream 是 Java 8 引入的函数式数据处理 API,主要特点:
java复制// 从集合创建
List<String> list = ...;
Stream<String> stream = list.stream();
// 从数组创建
String[] array = ...;
Stream<String> stream = Arrays.stream(array);
// 直接创建
Stream<Integer> nums = Stream.of(1, 2, 3);
java复制// 过滤
stream.filter(s -> s.length() > 3)
// 映射
stream.map(String::toUpperCase)
// 去重
stream.distinct()
// 排序
stream.sorted()
// 截取
stream.limit(10)
java复制// 收集为List
List<String> result = stream.collect(Collectors.toList());
// 遍历
stream.forEach(System.out::println);
// 匹配检查
boolean anyMatch = stream.anyMatch(s -> s.startsWith("A"));
// 聚合计算
long count = stream.count();
Optional<String> max = stream.max(Comparator.naturalOrder());
java复制// 按属性分组
Map<Department, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
// 多级分组
Map<Department, Map<JobTitle, List<Employee>>> byDeptAndTitle = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment,
Collectors.groupingBy(Employee::getJobTitle)));
// 分区(true/false两组)
Map<Boolean, List<Employee>> partitioned = employees.stream()
.collect(Collectors.partitioningBy(e -> e.getSalary() > 100000));
对于大数据集,可以使用并行流提高处理速度:
java复制List<String> result = largeList.parallelStream()
.filter(...)
.map(...)
.collect(Collectors.toList());
注意:并行流不总是更快,需要考虑数据规模、操作成本和线程开销。
JDK 8 的 ConcurrentHashMap 采用 Node 数组 + 链表/红黑树结构,通过以下方式实现并发安全:
java复制ConcurrentMap<String, Integer> map = new ConcurrentHashMap<>();
// 线程安全的putIfAbsent
map.putIfAbsent("key", 1);
// 原子更新
map.compute("key", (k, v) -> v == null ? 1 : v + 1);
// 批量操作
map.forEach(2, (k, v) -> System.out.println(k + "=" + v));
适合读多写少的场景:
java复制List<String> list = new CopyOnWriteArrayList<>();
// 线程安全的添加
list.add("item");
// 迭代是安全的
for (String item : list) {
// 即使其他线程修改list也不会影响当前迭代
}
常用实现类:
java复制BlockingQueue<String> queue = new ArrayBlockingQueue<>(100);
// 生产者
queue.put("item");
// 消费者
String item = queue.take();
java复制// 传统方式
List<String> list = new ArrayList<>(100);
// Java 9+ 不可变集合
List<String> smallList = List.of("a", "b", "c");
Set<String> smallSet = Set.of("x", "y");
Map<String, Integer> smallMap = Map.of("a", 1, "b", 2);
| 需求 | 推荐实现 |
|---|---|
| 快速随机访问 | ArrayList |
| 频繁插入删除 | LinkedList |
| 去重 | HashSet |
| 有序去重 | TreeSet 或 LinkedHashSet |
| 键值对存储 | HashMap |
| 有序键值对 | TreeMap |
| 并发环境 | ConcurrentHashMap, CopyOnWriteArrayList |
equals 和 hashCode:
并发修改异常:
性能问题:
空集合处理:
Collections.emptyList(), Collections.emptyMap()下表总结了主要集合实现的操作时间复杂度:
| 操作 | ArrayList | LinkedList | HashSet | TreeSet | HashMap | TreeMap |
|---|---|---|---|---|---|---|
| 访问 | O(1) | O(n) | O(1) | O(log n) | O(1) | O(log n) |
| 插入 | O(n) | O(1) | O(1) | O(log n) | O(1) | O(log n) |
| 删除 | O(n) | O(1) | O(1) | O(log n) | O(1) | O(log n) |
| 搜索 | O(n) | O(n) | O(1) | O(log n) | O(1) | O(log n) |
java复制public Map<String, Integer> wordFrequency(List<String> words) {
Map<String, Integer> freq = new HashMap<>();
for (String word : words) {
freq.merge(word, 1, Integer::sum);
}
return freq;
}
// Java 8 Stream 方式
public Map<String, Long> wordFrequencyStream(List<String> words) {
return words.stream()
.collect(Collectors.groupingBy(
Function.identity(),
Collectors.counting()
));
}
java复制class Order {
private String customerId;
private BigDecimal amount;
// getters...
}
public Map<String, BigDecimal> totalByCustomer(List<Order> orders) {
return orders.stream()
.collect(Collectors.groupingBy(
Order::getCustomerId,
Collectors.reducing(
BigDecimal.ZERO,
Order::getAmount,
BigDecimal::add
)
));
}
java复制public class SimpleCache<K, V> {
private final Map<K, V> cache;
private final int maxSize;
public SimpleCache(int maxSize) {
this.maxSize = maxSize;
this.cache = new LinkedHashMap<K, V>(16, 0.75f, true) {
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > maxSize;
}
};
}
public synchronized V get(K key) {
return cache.get(key);
}
public synchronized void put(K key, V value) {
cache.put(key, value);
}
}
Java 集合框架是 Java 开发中最基础也是最重要的部分之一。掌握集合框架不仅能写出更高效的代码,还能更好地理解 Java 的设计思想。以下是一些进阶建议:
在实际开发中,要根据具体需求选择最合适的集合实现,并注意线程安全、性能优化等关键问题。通过合理使用集合框架,可以显著提高代码质量和执行效率。