集合类
学习目标
- 掌握
Collection与Map两大体系的接口层级与职责划分 - 理解
List/Set/Queue的行为差异与典型实现类(ArrayList / LinkedList / HashSet / TreeSet / PriorityQueue) - 区分有序/无序、允许重复/去重、线程安全/非安全等维度,能正确选型
- 理解
Iterator迭代器的fail-fast(快速失败)机制与ConcurrentModificationException - 了解各实现类的底层数据结构与性能特征(时间复杂度)
java.util 包中提供一些集合类,这些集合类又被称为容器。提到容器不难想到数组。集合类与数组的不同之处是:数组的长度是固定的,集合的长度是可变的;数组用来存放基本类型的数据,集合用来存放对象的引用。
java.util 包中提供一些集合类,这些集合类又被称为容器。提到容器不难想到数组。集合类与数组的不同之处是:数组的长度是固定的,集合的长度是可变的;数组用来存放基本类型的数据,集合用来存放对象的引用。
常用的集合有 List 集合、Set 集合、Queue 队列和 Map 集合,其中 List 集合、Set 集合与 Queue 队列继承 Collection 接口,各接口还提供了不同的实现类:
List:一种有序列表的集合,例如,按索引排列的Student的ListSet:一种保证没有重复元素的集合,例如,所有无重复名称的Student的SetQueue:一种先进先出的队列集合,例如,任务队列、消息队列Map:一种通过键值 (key-value) 查找的映射表集合,例如,根据Student的name查找对应Student的Map
集合框架体系
Java 集合框架主要由两个根接口派生而来:
-
Collection 接口:存储单个元素
- List 接口:有序、可重复
- Set 接口:无序、不可重复
- Queue 接口:队列,先进先出
-
Map 接口:存储键值对
继承体系图
Iterable (接口)
└── Collection (接口)
├── List (接口)
│ ├── ArrayList
│ ├── LinkedList
│ └── Vector
│ └── Stack
├── Set (接口)
│ ├── HashSet
│ │ └── LinkedHashSet
│ └── TreeSet
└── Queue (接口)
├── LinkedList
├── PriorityQueue
└── ArrayDeque
Map (接口)
├── HashMap
│ └── LinkedHashMap
├── TreeMap
├── Hashtable
│ └── Properties
└── ConcurrentHashMapList 集合
List 是一个有序集合,允许存储重复的元素,并且可以通过索引访问元素。它是 Collection 接口的子接口,提供了比 Collection 更丰富的操作方法。
List 接口继承自 Collection 接口,定义了一个有序的集合,支持以下特性:
- 有序性:元素按照插入顺序存储,可以通过索引访问
- 允许重复:可以存储多个相同的元素
- 索引访问:支持通过索引(从 0 开始)访问元素
核心方法
List 接口扩展了 Collection 接口的方法,并添加了一些与索引相关的操作。
添加元素
| 方法名 | 功能描述 |
|---|---|
boolean add(E e) | 在列表末尾添加一个元素 |
void add(int index, E element) | 在指定索引位置插入一个元素 |
boolean addAll(Collection<? extends E> c) | 将指定集合中的所有元素添加到列表末尾 |
boolean addAll(int index, Collection<? extends E> c) | 将指定集合中的所有元素插入到指定索引位置 |
删除元素
| 方法名 | 功能描述 |
|---|---|
E remove(int index) | 移除并返回指定索引位置的元素 |
boolean remove(Object o) | 移除列表中第一个匹配的元素(如果存在) |
boolean removeAll(Collection<?> c) | 移除列表中包含在指定集合中的所有元素 |
void clear() | 清空列表中的所有元素 |
修改元素
| 方法名 | 功能描述 |
|---|---|
E set(int index, E element) | 替换指定索引位置的元素,并返回被替换的元素 |
查询元素
| 方法名 | 功能描述 |
|---|---|
E get(int index) | 返回指定索引位置的元素 |
int indexOf(Object o) | 返回指定元素的第一次出现的索引(如果不存在,返回 -1) |
int lastIndexOf(Object o) | 返回指定元素的最后一次出现的索引(如果不存在,返回 -1) |
List<E> subList(int fromIndex, int toIndex) | 返回从 fromIndex 到 toIndex 的子列表(左闭右开区间) |
遍历集合
| 方法名 | 功能描述 |
|---|---|
Iterator<E> iterator() | 返回一个迭代器,用于遍历列表中的元素 |
ListIterator<E> listIterator() | 返回一个双向迭代器,支持从前往后和从后往前遍历列表 |
ListIterator<E> listIterator(int index) | 返回一个从指定索引开始的双向迭代器 |
排序和批量操作
| 方法名 | 功能描述 |
|---|---|
default void sort(Comparator<? super E> c) | 对列表中的元素进行排序(需要 Java 8+)。 |
boolean containsAll(Collection<?> c) | 判断列表是否包含指定集合中的所有元素。 |
boolean equals(Object o) | 比较两个列表是否相等(元素顺序和内容相同)。 |
int hashCode() | 返回列表的哈希码值。 |
流式操作(Java 8+)
| 方法名 | 功能描述 |
|---|---|
Spliterator<E> spliterator() | 返回一个 Spliterator,用于并行遍历列表中的元素。 |
Stream<E> stream() | 返回一个顺序流(Stream),用于对列表进行流式操作。 |
Stream<E> parallelStream() | 返回一个并行流(Parallel Stream),用于并行处理列表中的元素。 |
List 接口的实现类
List 接口有多个实现类,每个实现类都有不同的特性和适用场景。
ArrayList
- 底层实现:基于动态数组
- 特点:
- 支持快速随机访问(通过索引访问元素的时间复杂度为 O(1))
- 插入和删除效率较低(特别是在中间位置,时间复杂度为 O(n))
- 非线程安全
- 适用场景:适合频繁读取和随机访问的场景
import java.util.ArrayList;
import java.util.List;
public class ArrayListExample {
public static void main(String[] args) {
List<String> arrayList = new ArrayList<>();
arrayList.add("Apple");
arrayList.add("Banana");
arrayList.add("Cherry");
System.out.println("ArrayList: " + arrayList); // 输出: [Apple, Banana, Cherry]
System.out.println("Element at index 1: " + arrayList.get(1)); // 输出: Banana
}
}ArrayList 扩容机制:
- 初始容量为 10
- 当容量不足时,扩容为原来的 1.5 倍(
int newCapacity = oldCapacity + (oldCapacity >> 1)) - 扩容时会创建新数组,将原数组元素复制过去
LinkedList
- 底层实现:基于双向链表
- 特点:
- 插入和删除效率高(特别是在中间位置,时间复杂度为 O(1))
- 随机访问效率低(通过索引访问元素的时间复杂度为 O(n))
- 非线程安全
- 实现了
List和Deque接口,可以作为列表、队列、栈使用
- 适用场景:适合频繁插入和删除的场景
import java.util.LinkedList;
import java.util.List;
public class LinkedListExample {
public static void main(String[] args) {
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("Apple");
linkedList.add("Banana");
linkedList.add("Cherry");
System.out.println("LinkedList: " + linkedList); // 输出: [Apple, Banana, Cherry]
System.out.println("First Element: " + linkedList.getFirst()); // 输出: Apple
System.out.println("Last Element: " + linkedList.getLast()); // 输出: Cherry
// 作为栈使用
linkedList.push("Durian"); // 等同于 addFirst
System.out.println("Stack pop: " + linkedList.pop()); // 等同于 removeFirst
// 作为队列使用
linkedList.offer("Elderberry"); // 等同于 add/addLast
System.out.println("Queue poll: " + linkedList.poll()); // 等同于 removeFirst
}
}Vector
- 底层实现:基于动态数组
- 特点:
- 线程安全(所有方法都使用
synchronized修饰) - 性能较低,已被
ArrayList取代 - 扩容机制:默认扩容为原来的 2 倍
- 线程安全(所有方法都使用
- 适用场景:需要线程安全的场景(但推荐使用
CopyOnWriteArrayList)
import java.util.Vector;
public class VectorExample {
public static void main(String[] args) {
Vector<String> vector = new Vector<>();
vector.add("Apple");
vector.add("Banana");
System.out.println("Vector: " + vector);
// Stack 是 Vector 的子类
java.util.Stack<String> stack = new java.util.Stack<>();
stack.push("A");
stack.push("B");
System.out.println("Stack pop: " + stack.pop()); // 输出: B
}
}CopyOnWriteArrayList
- 底层实现:基于写时复制机制的线程安全列表
- 特点:
- 线程安全
- 读操作不需要加锁,性能较高
- 写操作会复制整个数组,适合读多写少的场景
- 适用场景:适合高并发读取、低并发写入的场景
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteArrayListExample {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("Apple");
list.add("Banana");
System.out.println("List: " + list); // 输出: [Apple, Banana]
// 适合并发遍历
list.forEach(System.out::println);
}
}常用操作示例
遍历列表
使用 for 循环
import java.util.ArrayList;
import java.util.List;
public class ForLoopExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}使用增强 for 循环(for-each)
import java.util.ArrayList;
import java.util.List;
public class ForEachLoopExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
for (String item : list) {
System.out.println(item);
}
}
}使用 forEach 方法(Java 8+)
import java.util.ArrayList;
import java.util.List;
public class ForEachExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.forEach(System.out::println);
}
}使用迭代器
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class IteratorExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}使用 ListIterator(双向遍历)
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class ListIteratorExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// 正向遍历
ListIterator<String> iterator = list.listIterator();
while (iterator.hasNext()) {
System.out.println("Index " + iterator.nextIndex() + ": " + iterator.next());
}
// 反向遍历
System.out.println("\nReverse traversal:");
while (iterator.hasPrevious()) {
System.out.println("Index " + iterator.previousIndex() + ": " + iterator.previous());
}
}
}排序
对列表进行排序
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SortExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Banana");
list.add("Apple");
list.add("Cherry");
Collections.sort(list); // 默认按自然顺序排序
System.out.println("Sorted List: " + list); // 输出: [Apple, Banana, Cherry]
}
}自定义排序
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class CustomSortExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Banana");
list.add("Apple");
list.add("Cherry");
list.sort(Comparator.reverseOrder()); // 按降序排序
System.out.println("Reverse Sorted List: " + list); // 输出: [Cherry, Banana, Apple]
}
}流式操作
示例:过滤和映射
import java.util.ArrayList;
import java.util.List;
public class StreamExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// 过滤以 'A' 开头的元素并转换为大写
List<String> result = list.stream()
.filter(s -> s.startsWith("A"))
.map(String::toUpperCase)
.toList();
System.out.println(result); // 输出: [APPLE]
}
}Queue 队列
Queue 是一个**先进先出(FIFO)**的队列集合,继承自 Collection 接口。队列通常用于存储待处理的任务或消息。
核心方法
| 方法名 | 功能描述 | 失败时行为 |
|---|---|---|
boolean add(E e) | 添加元素到队列尾部 | 抛出异常 |
boolean offer(E e) | 添加元素到队列尾部 | 返回 false |
E remove() | 移除并返回队列头部元素 | 抛出异常 |
E poll() | 移除并返回队列头部元素 | 返回 null |
E element() | 返回队列头部元素但不移除 | 抛出异常 |
E peek() | 返回队列头部元素但不移除 | 返回 null |
Queue 接口的实现类
LinkedList
- 底层实现:基于双向链表
- 特点:
- 实现了
List和Deque接口 - 可以作为队列、栈、列表使用
- 非线程安全
- 实现了
- 适用场景:适合频繁插入和删除的场景
import java.util.LinkedList;
import java.util.Queue;
public class LinkedListQueueExample {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
queue.offer("A");
queue.offer("B");
queue.offer("C");
System.out.println("Queue: " + queue); // 输出: [A, B, C]
System.out.println("Poll: " + queue.poll()); // 输出: A
System.out.println("Peek: " + queue.peek()); // 输出: B
}
}PriorityQueue
- 底层实现:基于堆的二叉树
- 特点:
- 元素按优先级排序(默认自然顺序,可自定义比较器)
- 不允许 null 元素
- 非线程安全
- 适用场景:需要按优先级处理元素的场景
import java.util.PriorityQueue;
import java.util.Queue;
public class PriorityQueueExample {
public static void main(String[] args) {
// 默认最小堆(元素按从小到大排序)
Queue<Integer> queue = new PriorityQueue<>();
queue.offer(5);
queue.offer(1);
queue.offer(3);
System.out.println("PriorityQueue: " + queue);
// 按优先级取出元素
while (!queue.isEmpty()) {
System.out.println(queue.poll());
}
// 输出顺序: 1, 3, 5
// 自定义比较器:最大堆
Queue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
maxHeap.offer(5);
maxHeap.offer(1);
maxHeap.offer(3);
System.out.println("\nMax Heap:");
while (!maxHeap.isEmpty()) {
System.out.println(maxHeap.poll());
}
// 输出顺序: 5, 3, 1
}
}ArrayDeque
- 底层实现:基于循环数组
- 特点:
- 可以作为栈和队列使用
- 效率高于
Stack和LinkedList - 不允许 null 元素
- 非线程安全
- 适用场景:需要栈或队列功能的场景
import java.util.ArrayDeque;
import java.util.Deque;
public class ArrayDequeExample {
public static void main(String[] args) {
// 作为栈使用
Deque<String> stack = new ArrayDeque<>();
stack.push("A");
stack.push("B");
stack.push("C");
System.out.println("Stack pop: " + stack.pop()); // 输出: C
System.out.println("Stack peek: " + stack.peek()); // 输出: B
// 作为队列使用
Deque<String> queue = new ArrayDeque<>();
queue.offer("A");
queue.offer("B");
queue.offer("C");
System.out.println("\nQueue poll: " + queue.poll()); // 输出: A
System.out.println("Queue peek: " + queue.peek()); // 输出: B
}
}Set 集合
Set 是一个不包含重复元素的集合,它继承自 Collection 接口。Set 接口不保证元素的顺序,但某些实现类(如 LinkedHashSet)可以维护插入顺序。
Set 接口的主要特性:
- 无重复性:不包含重复元素,最多包含一个 null 元素
- 无序性:不保证元素的顺序(某些实现类除外)
- 最多一个 null 元素:大多数 Set 实现允许包含一个 null 元素
核心方法
Set 接口继承自 Collection 接口,没有添加新的方法,但重写了一些方法以确保不重复的特性。
| 方法名 | 功能描述 |
|---|---|
boolean add(E e) | 添加元素,如果元素已存在则返回 false |
boolean remove(Object o) | 移除指定元素,如果元素存在则返回 true |
boolean contains(Object o) | 判断集合是否包含指定元素 |
int size() | 返回集合中的元素数量 |
boolean isEmpty() | 判断集合是否为空 |
Iterator<E> iterator() | 返回迭代器,用于遍历集合中的元素 |
Object[] toArray() | 将集合转换为数组 |
boolean containsAll(Collection<?> c) | 判断集合是否包含指定集合中的所有元素 |
boolean addAll(Collection<? extends E> c) | 将指定集合中的所有元素添加到集合中 |
boolean retainAll(Collection<?> c) | 仅保留集合中包含在指定集合中的元素 |
boolean removeAll(Collection<?> c) | 移除集合中包含在指定集合中的所有元素 |
void clear() | 移除集合中的所有元素 |
Set 接口的实现类
HashSet
- 底层实现:基于哈希表(实际上是
HashMap实例) - 特点:
- 不保证元素的顺序
- 允许 null 元素
- 非线程安全
- 查找、插入和删除的时间复杂度都是 O(1)
- 适用场景:需要快速查找、不关心元素顺序的场景
import java.util.HashSet;
import java.util.Set;
public class HashSetExample {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("Apple");
set.add("Banana");
set.add("Cherry");
set.add("Apple"); // 重复元素,不会被添加
System.out.println("HashSet: " + set); // 输出顺序可能不同
System.out.println("Contains 'Apple': " + set.contains("Apple")); // 输出: true
System.out.println("Size: " + set.size()); // 输出: 3
}
}HashSet 判断元素重复的原理:
- 先调用元素的
hashCode()方法计算哈希值 - 如果哈希值相同,再调用
equals()方法比较 - 如果两个方法都返回 true,则认为是重复元素
LinkedHashSet
- 底层实现:基于哈希表和链表,继承自
HashSet - 特点:
- 维护元素的插入顺序
- 允许 null 元素
- 非线程安全
- 性能略低于
HashSet,但仍然很好
- 适用场景:需要维护插入顺序且需要快速查找的场景
import java.util.LinkedHashSet;
import java.util.Set;
public class LinkedHashSetExample {
public static void main(String[] args) {
Set<String> set = new LinkedHashSet<>();
set.add("Apple");
set.add("Banana");
set.add("Cherry");
System.out.println("LinkedHashSet: " + set); // 输出: [Apple, Banana, Cherry],保持插入顺序
}
}TreeSet
- 底层实现:基于红黑树(NavigableMap 实现)
- 特点:
- 元素按自然顺序或自定义比较器排序
- 不允许 null 元素
- 非线程安全
- 查找、插入和删除的时间复杂度都是 O(log n)
- 适用场景:需要元素有序且需要快速查找的场景
import java.util.TreeSet;
import java.util.Set;
public class TreeSetExample {
public static void main(String[] args) {
Set<String> set = new TreeSet<>();
set.add("Banana");
set.add("Apple");
set.add("Cherry");
System.out.println("TreeSet: " + set); // 输出: [Apple, Banana, Cherry],按字母顺序排序
// 自定义比较器
Set<String> reverseSet = new TreeSet<>((a, b) -> b.compareTo(a));
reverseSet.add("Banana");
reverseSet.add("Apple");
reverseSet.add("Cherry");
System.out.println("Reverse TreeSet: " + reverseSet); // 输出: [Cherry, Banana, Apple]
}
}EnumSet
- 底层实现:基于位向量的专用 Set 实现
- 特点:
- 专门用于枚举类型
- 高效且内存占用小
- 按枚举常量的自然顺序排序
- 非线程安全
- 适用场景:需要存储枚举类型元素的场景
import java.util.EnumSet;
import java.util.Set;
public class EnumSetExample {
enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
public static void main(String[] args) {
Set<Day> weekend = EnumSet.of(Day.SATURDAY, Day.SUNDAY);
Set<Day> weekdays = EnumSet.range(Day.MONDAY, Day.FRIDAY);
System.out.println("Weekend: " + weekend); // 输出: [SATURDAY, SUNDAY]
System.out.println("Weekdays: " + weekdays); // 输出: [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY]
}
}常用操作示例
集合运算
import java.util.HashSet;
import java.util.Set;
public class SetOperationsExample {
public static void main(String[] args) {
Set<Integer> set1 = new HashSet<>();
Set<Integer> set2 = new HashSet<>();
// 添加元素
set1.add(1);
set1.add(2);
set1.add(3);
set2.add(2);
set2.add(3);
set2.add(4);
// 并集
Set<Integer> union = new HashSet<>(set1);
union.addAll(set2);
System.out.println("Union: " + union); // 输出: [1, 2, 3, 4]
// 交集
Set<Integer> intersection = new HashSet<>(set1);
intersection.retainAll(set2);
System.out.println("Intersection: " + intersection); // 输出: [2, 3]
// 差集
Set<Integer> difference = new HashSet<>(set1);
difference.removeAll(set2);
System.out.println("Difference: " + difference); // 输出: [1]
}
}自定义对象的 Set
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
class Student {
private String id;
private String name;
public Student(String id, String name) {
this.id = id;
this.name = name;
}
// 必须重写 equals() 和 hashCode() 方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(id, student.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString() {
return "Student{id='" + id + "', name='" + name + "'}";
}
}
public class CustomObjectSetExample {
public static void main(String[] args) {
Set<Student> students = new HashSet<>();
students.add(new Student("001", "张三"));
students.add(new Student("002", "李四"));
students.add(new Student("001", "王五")); // 不会添加,因为 id 相同
System.out.println("Students: " + students);
}
}Map 集合
Map 是一种存储键值对(key-value)的集合,它不继承自 Collection 接口。每个键最多只能映射到一个值,键不能重复,但值可以重复。
Map 接口的主要特性:
- 键值对存储:存储键值对,通过键访问值
- 键唯一性:键不能重复,每个键最多映射一个值
- 值可重复:不同的键可以映射相同的值
- 允许一个 null 键和多个 null 值(取决于具体实现)
核心方法
| 方法名 | 功能描述 |
|---|---|
V put(K key, V value) | 添加键值对,如果键已存在则替换旧值 |
V get(Object key) | 返回指定键映射的值,如果不存在则返回 null |
V remove(Object key) | 移除指定键的映射关系,并返回对应的值 |
boolean containsKey(Object key) | 判断是否包含指定键 |
boolean containsValue(Object value) | 判断是否包含指定值 |
int size() | 返回键值对的数量 |
boolean isEmpty() | 判断是否为空 |
void clear() | 清空所有键值对 |
Set<K> keySet() | 返回所有键的集合 |
Collection<V> values() | 返回所有值的集合 |
Set<Map.Entry<K, V>> entrySet() | 返回所有键值对的集合 |
void putAll(Map<? extends K, ? extends V> m) | 将指定映射中的所有映射关系添加到此映射中 |
Java 8+ 新增方法
| 方法名 | 功能描述 |
|---|---|
V getOrDefault(Object key, V defaultValue) | 获取指定键的值,如果不存在则返回默认值 |
V putIfAbsent(K key, V value) | 如果键不存在才添加 |
boolean remove(Object key, Object value) | 仅当键和值都匹配时才移除 |
boolean replace(K key, V oldValue, V newValue) | 仅当键和旧值都匹配时才替换 |
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) | 合并键值对 |
Map 接口的实现类
HashMap
- 底层实现:基于哈希表(数组+链表/红黑树)
- 特点:
- 不保证映射的顺序
- 允许一个 null 键和多个 null 值
- 非线程安全
- 查找、插入和删除的时间复杂度平均为 O(1)
- 适用场景:需要快速查找键值对的场景
import java.util.HashMap;
import java.util.Map;
public class HashMapExample {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("Apple", 10);
map.put("Banana", 20);
map.put("Cherry", 30);
map.put("Apple", 15); // 替换旧值
System.out.println("Map: " + map);
System.out.println("Value of 'Apple': " + map.get("Apple")); // 输出: 15
System.out.println("Contains key 'Banana': " + map.containsKey("Banana")); // 输出: true
System.out.println("Size: " + map.size()); // 输出: 3
// Java 8+ 新方法
System.out.println("Value of 'Date': " + map.getOrDefault("Date", 0)); // 输出: 0
map.putIfAbsent("Banana", 25); // 不会替换,因为键已存在
map.putIfAbsent("Date", 40); // 会添加
System.out.println("After putIfAbsent: " + map);
}
}HashMap 扩容机制:
- 初始容量为 16,默认负载因子为 0.75
- 当元素数量 > 容量 * 负载因子时,扩容为原来的 2 倍
- JDK 1.8 开始,当链表长度 > 8 且数组长度 >= 64 时,链表转为红黑树
- 当红黑树节点数 < 6 时,红黑树转回链表
LinkedHashMap
- 底层实现:基于哈希表和双向链表,继承自
HashMap - 特点:
- 维护键值对的插入顺序或访问顺序
- 允许一个 null 键和多个 null 值
- 非线程安全
- 性能略低于
HashMap,但仍然很好
- 适用场景:需要维护键值对顺序的场景
import java.util.LinkedHashMap;
import java.util.Map;
public class LinkedHashMapExample {
public static void main(String[] args) {
// 按插入顺序
Map<String, Integer> map = new LinkedHashMap<>();
map.put("Apple", 10);
map.put("Banana", 20);
map.put("Cherry", 30);
System.out.println("LinkedHashMap: " + map); // 输出: {Apple=10, Banana=20, Cherry=30},保持插入顺序
// 按访问顺序(可用于实现 LRU 缓存)
Map<String, Integer> lruMap = new LinkedHashMap<>(16, 0.75f, true);
lruMap.put("A", 1);
lruMap.put("B", 2);
lruMap.put("C", 3);
lruMap.get("A"); // 访问 A
System.out.println("LRU Map: " + lruMap); // 输出: {B=2, C=3, A=1},A 移到最后
}
}TreeMap
- 底层实现:基于红黑树(NavigableMap 实现)
- 特点:
- 键按自然顺序或自定义比较器排序
- 不允许 null 键,但允许 null 值
- 非线程安全
- 查找、插入和删除的时间复杂度为 O(log n)
- 适用场景:需要键有序且需要快速查找的场景
import java.util.Map;
import java.util.TreeMap;
public class TreeMapExample {
public static void main(String[] args) {
Map<String, Integer> map = new TreeMap<>();
map.put("Banana", 20);
map.put("Apple", 10);
map.put("Cherry", 30);
System.out.println("TreeMap: " + map); // 输出: {Apple=10, Banana=20, Cherry=30},按键排序
// 导航方法
TreeMap<String, Integer> treeMap = new TreeMap<>(map);
System.out.println("First key: " + treeMap.firstKey()); // 输出: Apple
System.out.println("Last key: " + treeMap.lastKey()); // 输出: Cherry
System.out.println("Lower key of 'Banana': " + treeMap.lowerKey("Banana")); // 输出: Apple
System.out.println("Higher key of 'Banana': " + treeMap.higherKey("Banana")); // 输出: Cherry
}
}Hashtable
- 底层实现:基于哈希表
- 特点:
- 线程安全(使用 synchronized 同步)
- 不允许 null 键和 null 值
- 性能低于
HashMap
- 适用场景:需要线程安全的键值对存储(但通常推荐使用
ConcurrentHashMap)
import java.util.Hashtable;
import java.util.Map;
public class HashtableExample {
public static void main(String[] args) {
Map<String, Integer> map = new Hashtable<>();
map.put("Apple", 10);
map.put("Banana", 20);
map.put("Cherry", 30);
System.out.println("Hashtable: " + map);
}
}ConcurrentHashMap
- 底层实现:基于分段锁或 CAS 操作的并发哈希表
- 特点:
- 线程安全,性能优于
Hashtable - 不允许 null 键和 null 值
- 支持高并发访问
- 线程安全,性能优于
- 适用场景:高并发环境下的键值对存储
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
Map<String, Integer> map = new ConcurrentHashMap<>();
map.put("Apple", 10);
map.put("Banana", 20);
map.put("Cherry", 30);
System.out.println("ConcurrentHashMap: " + map);
// 线程安全的操作
map.computeIfAbsent("Date", k -> 40);
System.out.println("After computeIfAbsent: " + map);
}
}常用操作示例
遍历 Map
import java.util.HashMap;
import java.util.Map;
public class MapTraversalExample {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("Apple", 10);
map.put("Banana", 20);
map.put("Cherry", 30);
// 遍历键
System.out.println("Keys:");
for (String key : map.keySet()) {
System.out.println(key);
}
// 遍历值
System.out.println("\nValues:");
for (Integer value : map.values()) {
System.out.println(value);
}
// 遍历键值对(推荐)
System.out.println("\nEntries:");
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
// Java 8+ forEach 方法
System.out.println("\nUsing forEach:");
map.forEach((key, value) -> System.out.println(key + " = " + value));
}
}使用自定义对象作为键
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
class Person {
private String id;
private String name;
public Person(String id, String name) {
this.id = id;
this.name = name;
}
// 必须重写 equals() 和 hashCode() 方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(id, person.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString() {
return "Person{id='" + id + "', name='" + name + "'}";
}
}
public class CustomKeyMapExample {
public static void main(String[] args) {
Map<Person, String> map = new HashMap<>();
map.put(new Person("001", "张三"), "工程师");
map.put(new Person("002", "李四"), "设计师");
map.put(new Person("001", "王五"), "教师"); // 替换旧值,因为 id 相同
System.out.println("Map:");
map.forEach((person, job) -> System.out.println(person + " -> " + job));
}
}迭代器
迭代器(Iterator)是一种设计模式,用于遍历集合中的元素。Java 集合框架中的 Collection 接口继承了 Iterable 接口,因此所有集合都可以使用迭代器遍历。
Iterator 接口
Iterator 接口提供了基本的遍历功能:
| 方法名 | 功能描述 |
|---|---|
boolean hasNext() | 判断是否还有下一个元素 |
E next() | 返回下一个元素 |
default void remove() | 移除当前元素(可选操作) |
default void forEachRemaining(Consumer<? super E> action) | 对剩余元素执行操作(Java 8+) |
ListIterator 接口
ListIterator 继承自 Iterator,提供了双向遍历和修改功能:
| 方法名 | 功能描述 |
|---|---|
boolean hasPrevious() | 判断是否还有前一个元素 |
E previous() | 返回前一个元素 |
int nextIndex() | 返回下一个元素的索引 |
int previousIndex() | 返回前一个元素的索引 |
void set(E e) | 替换当前元素 |
void add(E e) | 在当前位置插入元素 |
使用示例
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
public class IteratorExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
// 使用 Iterator 遍历并删除元素
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
if ("B".equals(element)) {
iterator.remove(); // 安全删除
}
}
System.out.println("After remove: " + list); // 输出: [A, C]
// 使用 ListIterator 遍历并修改元素
ListIterator<String> listIterator = list.listIterator();
while (listIterator.hasNext()) {
String element = listIterator.next();
listIterator.set(element.toLowerCase());
}
System.out.println("After set: " + list); // 输出: [a, c]
// 反向遍历
System.out.println("\nReverse:");
while (listIterator.hasPrevious()) {
System.out.println(listIterator.previous());
}
}
}迭代器的注意事项
- 并发修改异常:在使用迭代器遍历集合时,如果通过集合的方法(非迭代器的
remove/add/set)修改集合,会抛出ConcurrentModificationException
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ConcurrentModificationExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
// 错误:在迭代过程中通过集合方法修改
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
if ("B".equals(element)) {
// list.remove(element); // 抛出 ConcurrentModificationException
}
}
// 正确:使用迭代器的 remove 方法
iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
if ("B".equals(element)) {
iterator.remove(); // 安全
}
}
}
}- 增强 for 循环的本质:增强 for 循环(for-each)底层使用迭代器实现,因此也不能在遍历过程中通过集合方法修改集合
import java.util.ArrayList;
import java.util.List;
public class ForEachLimitationExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
// 错误:会抛出 ConcurrentModificationException
for (String element : list) {
if ("B".equals(element)) {
// list.remove(element); // 错误
}
}
// 正确:使用迭代器或 removeIf(Java 8+)
list.removeIf(element -> "B".equals(element));
System.out.println("After removeIf: " + list); // 输出: [A, C]
}
}集合的线程安全问题
非线程安全的集合
大多数集合类(如 ArrayList、HashMap、HashSet)都是非线程安全的。在多线程环境下使用这些集合可能导致:
- 数据不一致
- 索引越界异常
- 死循环(如
HashMap在并发扩容时) ConcurrentModificationException
线程安全的解决方案
1. 使用同步包装器
Collections 工具类提供了同步包装器方法:
import java.util.*;
public class SynchronizedCollectionExample {
public static void main(String[] args) {
// 同步的 List
List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());
// 同步的 Set
Set<String> synchronizedSet = Collections.synchronizedSet(new HashSet<>());
// 同步的 Map
Map<String, Integer> synchronizedMap = Collections.synchronizedMap(new HashMap<>());
// 注意:迭代时需要手动同步
synchronized (synchronizedList) {
for (String element : synchronizedList) {
System.out.println(element);
}
}
}
}2. 使用并发集合(推荐)
java.util.concurrent 包提供了高性能的并发集合:
import java.util.*;
import java.util.concurrent.*;
public class ConcurrentCollectionExample {
public static void main(String[] args) {
// 并发的 List
List<String> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
// 并发的 Set
Set<String> copyOnWriteArraySet = new CopyOnWriteArraySet<>();
// 并发的 Map
Map<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();
// 并发的 Queue
Queue<String> concurrentLinkedQueue = new ConcurrentLinkedQueue<>();
// 阻塞队列
BlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(10);
BlockingQueue<String> linkedBlockingQueue = new LinkedBlockingQueue<>();
}
}3. 使用 synchronized 关键字
import java.util.*;
public class SynchronizedBlockExample {
private List<String> list = new ArrayList<>();
public void add(String element) {
synchronized (list) {
list.add(element);
}
}
public String get(int index) {
synchronized (list) {
return list.get(index);
}
}
}并发集合性能对比
| 集合类型 | 读性能 | 写性能 | 适用场景 |
|---|---|---|---|
Hashtable | 低 | 低 | 不推荐使用 |
Collections.synchronizedXxx | 中 | 中 | 需要完全同步的场景 |
ConcurrentHashMap | 高 | 高 | 高并发读写场景(推荐) |
CopyOnWriteArrayList | 高 | 低 | 读多写少的场景 |
Arrays 工具类
Arrays 类提供了操作数组的静态方法。
常用方法
| 方法名 | 功能描述 |
|---|---|
static <T> List<T> asList(T... a) | 将数组转为 List(固定大小) |
static void sort(int[] a) | 对数组排序 |
static int binarySearch(int[] a, int key) | 二分查找(数组必须有序) |
static void fill(int[] a, int val) | 填充数组 |
static int[] copyOf(int[] original, int newLength) | 复制数组 |
static String toString(int[] a) | 数组转字符串 |
static boolean equals(int[] a, int[] a2) | 比较数组是否相等 |
使用示例
import java.util.Arrays;
import java.util.List;
public class ArraysExample {
public static void main(String[] args) {
// asList:数组转 List
String[] array = {"A", "B", "C"};
List<String> list = Arrays.asList(array);
System.out.println("List: " + list);
// 注意:asList 返回的 List 大小固定,不能添加或删除元素
// list.add("D"); // 抛出 UnsupportedOperationException
// 如果需要可变 List,需要用 new ArrayList<>(Arrays.asList(array))
// sort:排序
int[] numbers = {3, 1, 4, 1, 5, 9};
Arrays.sort(numbers);
System.out.println("Sorted: " + Arrays.toString(numbers));
// binarySearch:二分查找
int index = Arrays.binarySearch(numbers, 4);
System.out.println("Index of 4: " + index);
// fill:填充
int[] filled = new int[5];
Arrays.fill(filled, 10);
System.out.println("Filled: " + Arrays.toString(filled));
// copyOf:复制
int[] copied = Arrays.copyOf(numbers, 3);
System.out.println("Copied: " + Arrays.toString(copied));
// equals:比较
int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
System.out.println("Equals: " + Arrays.equals(a, b)); // 输出: true
}
}Collections 工具类
Collections 类提供了操作集合的静态方法,包括排序、搜索、同步等。
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CollectionsExample {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(3);
list.add(1);
list.add(4);
list.add(2);
System.out.println("Original list: " + list);
// 排序
Collections.sort(list);
System.out.println("Sorted list: " + list);
// 反转
Collections.reverse(list);
System.out.println("Reversed list: " + list);
// 洗牌
Collections.shuffle(list);
System.out.println("Shuffled list: " + list);
// 查找最大值和最小值
System.out.println("Max: " + Collections.max(list));
System.out.println("Min: " + Collections.min(list));
// 查找元素索引(列表必须有序)
Collections.sort(list);
System.out.println("Binary search: " + Collections.binarySearch(list, 3));
// 替换所有元素
Collections.replaceAll(list, 3, 10);
System.out.println("After replaceAll: " + list);
// 填充
Collections.fill(list, 0);
System.out.println("After fill: " + list);
// 创建不可修改的集合
List<Integer> unmodifiableList = Collections.unmodifiableList(new ArrayList<>(list));
// unmodifiableList.add(5); // 抛出 UnsupportedOperationException
// 创建同步的集合
List<Integer> synchronizedList = Collections.synchronizedList(list);
// 创建单元素集合
List<Integer> singletonList = Collections.singletonList(1);
System.out.println("Singleton list: " + singletonList);
// 创建空集合
List<Integer> emptyList = Collections.emptyList();
System.out.println("Empty list: " + emptyList);
}
}泛型与集合
泛型提供了编译时类型安全,允许在集合中存储特定类型的对象。
import java.util.ArrayList;
import java.util.List;
public class GenericCollectionExample {
public static void main(String[] args) {
// 使用泛型指定集合中存储的元素类型
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
stringList.add("World");
// stringList.add(123); // 编译错误,不能添加非 String 类型的元素
// 泛型方法
List<Integer> intList = createList(1, 2, 3);
System.out.println("Integer list: " + intList);
}
// 泛型方法
@SafeVarargs
public static <T> List<T> createList(T... items) {
List<T> list = new ArrayList<>();
for (T item : items) {
list.add(item);
}
return list;
}
}Java 8+ 集合新特性
Java 8 引入了许多集合操作的新特性,特别是 Stream API。
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Java8CollectionFeatures {
public static void main(String[] args) {
List<String> list = Arrays.asList("Apple", "Banana", "Cherry", "Date");
// 过滤和转换
List<String> filtered = list.stream()
.filter(s -> s.length() > 5)
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("Filtered list: " + filtered);
// 分组
Map<Integer, List<String>> groupedByLength = list.stream()
.collect(Collectors.groupingBy(String::length));
System.out.println("Grouped by length: " + groupedByLength);
// 求和、平均值等
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().mapToInt(Integer::intValue).sum();
double average = numbers.stream().mapToInt(Integer::intValue).average().orElse(0);
System.out.println("Sum: " + sum);
System.out.println("Average: " + average);
// 并行流
int parallelSum = numbers.parallelStream().mapToInt(Integer::intValue).sum();
System.out.println("Parallel sum: " + parallelSum);
}
}集合选择指南
如何选择合适的集合类型
| 场景 | 推荐集合类型 | 原因 |
|---|---|---|
| 需要存储有序元素,允许重复 | ArrayList | 随机访问效率高 |
| 频繁插入和删除元素 | LinkedList | 插入和删除效率高 |
| 需要存储不重复元素,不关心顺序 | HashSet | 查找效率高 |
| 需要存储不重复元素,保持插入顺序 | LinkedHashSet | 维护插入顺序 |
| 需要存储不重复元素,元素有序 | TreeSet | 元素自动排序 |
| 需要存储键值对,不关心顺序 | HashMap | 查找效率高 |
| 需要存储键值对,保持插入顺序 | LinkedHashMap | 维护插入顺序 |
| 需要存储键值对,键有序 | TreeMap | 键自动排序 |
| 需要队列功能 | LinkedList 或 ArrayDeque | 效率高 |
| 需要优先级队列 | PriorityQueue | 按优先级排序 |
| 需要栈功能 | ArrayDeque | 效率高于 Stack |
| 多线程环境,需要线程安全的 List | CopyOnWriteArrayList | 读多写少性能好 |
| 多线程环境,需要线程安全的 Map | ConcurrentHashMap | 高并发性能好 |
性能对比
| 集合类型 | 查找 | 插入 | 删除 | 空间占用 |
|---|---|---|---|---|
ArrayList | O(1) | O(n) | O(n) | 低 |
LinkedList | O(n) | O(1) | O(1) | 高(存储指针) |
HashSet | O(1) | O(1) | O(1) | 中 |
TreeSet | O(log n) | O(log n) | O(log n) | 高(红黑树) |
HashMap | O(1) | O(1) | O(1) | 中 |
TreeMap | O(log n) | O(log n) | O(log n) | 高(红黑树) |
PriorityQueue | O(n) | O(log n) | O(log n) | 中 |
性能考虑
-
时间复杂度:
ArrayList、HashSet、HashMap:大多数操作为 O(1)LinkedList:插入和删除为 O(1),访问为 O(n)TreeSet、TreeMap:大多数操作为 O(log n)
-
空间复杂度:
ArrayList:空间利用率高LinkedList:每个元素需要额外空间存储指针HashSet、HashMap:需要额外空间存储哈希表TreeSet、TreeMap:需要额外空间存储红黑树
-
内存占用:
- 基本类型:考虑使用专门的基本类型集合(如
Trove库) - 大对象:考虑使用弱引用或软引用
- 基本类型:考虑使用专门的基本类型集合(如
常见误区
1. Arrays.asList() 返回的 List 不能添加或删除元素
import java.util.Arrays;
import java.util.List;
public class AsListPitfall {
public static void main(String[] args) {
String[] array = {"A", "B", "C"};
List<String> list = Arrays.asList(array);
// list.add("D"); // 抛出 UnsupportedOperationException
// list.remove(0); // 抛出 UnsupportedOperationException
// 正确做法:创建新的 ArrayList
List<String> mutableList = new ArrayList<>(Arrays.asList(array));
mutableList.add("D"); // 正常
System.out.println(mutableList);
}
}2. ArrayList 构造时指定容量
import java.util.ArrayList;
import java.util.List;
public class ArrayListCapacityExample {
public static void main(String[] args) {
// 如果知道元素数量,建议指定初始容量,避免多次扩容
List<String> list = new ArrayList<>(1000); // 初始容量 1000
// 添加 1000 个元素不会触发扩容
for (int i = 0; i < 1000; i++) {
list.add("Element" + i);
}
}
}3. subList() 返回的是视图,不是副本
import java.util.ArrayList;
import java.util.List;
public class SubListPitfall {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(i);
}
List<Integer> subList = list.subList(0, 5);
// 修改 subList 会影响原列表
subList.set(0, 100);
System.out.println("Original list: " + list); // 原列表也被修改
// 删除 subList 的元素也会影响原列表
subList.clear();
System.out.println("After clear: " + list); // 原列表的前 5 个元素被删除
// 注意:如果对原列表进行结构性修改(添加/删除),subList 的操作会抛出异常
}
}4. 自定义对象作为 HashMap 或 HashSet 的键时必须重写 equals() 和 hashCode()
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
class Student {
private String id;
private String name;
public Student(String id, String name) {
this.id = id;
this.name = name;
}
// 必须重写 equals() 和 hashCode()
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(id, student.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
public class CustomKeyPitfall {
public static void main(String[] args) {
Map<Student, Integer> map = new HashMap<>();
Student s1 = new Student("001", "张三");
Student s2 = new Student("001", "张三");
map.put(s1, 100);
System.out.println("Value: " + map.get(s2)); // 如果不重写 equals 和 hashCode,输出 null
}
}5. 使用 == 比较字符串
import java.util.HashMap;
import java.util.Map;
public class StringComparisonPitfall {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("key", 1);
String key = new String("key");
System.out.println(map.get(key)); // 输出: 1,因为 HashMap 使用 equals 比较
// 错误:使用 == 比较字符串
String a = "hello";
String b = new String("hello");
System.out.println(a == b); // 输出: false
System.out.println(a.equals(b)); // 输出: true
}
}实战案例
案例 1:统计单词频率
import java.util.HashMap;
import java.util.Map;
public class WordFrequencyExample {
public static void main(String[] args) {
String text = "hello world hello java world";
// 统计单词频率
Map<String, Integer> frequency = new HashMap<>();
for (String word : text.split(" ")) {
frequency.merge(word, 1, Integer::sum); // Java 8+ 方法
}
System.out.println("Word frequency: " + frequency);
// 输出: {hello=2, world=2, java=1}
// 找出出现次数最多的单词
frequency.entrySet().stream()
.max(Map.Entry.comparingByValue())
.ifPresent(entry -> System.out.println("Most frequent: " + entry.getKey()));
}
}案例 2:使用 LinkedHashMap 实现 LRU 缓存
import java.util.LinkedHashMap;
import java.util.Map;
class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int capacity;
public LRUCache(int capacity) {
super(capacity, 0.75f, true); // accessOrder = true
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacity;
}
}
public class LRUCacheExample {
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("Initial: " + cache);
cache.get("A"); // 访问 A
cache.put("D", 4); // 添加 D,B 被淘汰
System.out.println("After adding D: " + cache); // 输出: {C=3, A=1, D=4}
}
}案例 3:使用 PriorityQueue 实现任务调度
import java.util.PriorityQueue;
import java.util.Queue;
class Task implements Comparable<Task> {
private String name;
private int priority;
public Task(String name, int priority) {
this.name = name;
this.priority = priority;
}
@Override
public int compareTo(Task other) {
return Integer.compare(this.priority, other.priority); // 优先级小的先执行
}
@Override
public String toString() {
return "Task{name='" + name + "', priority=" + priority + "}";
}
}
public class TaskSchedulerExample {
public static void main(String[] args) {
Queue<Task> taskQueue = new PriorityQueue<>();
taskQueue.offer(new Task("Low priority task", 3));
taskQueue.offer(new Task("High priority task", 1));
taskQueue.offer(new Task("Medium priority task", 2));
// 按优先级执行任务
while (!taskQueue.isEmpty()) {
System.out.println("Executing: " + taskQueue.poll());
}
// 输出顺序: High priority task -> Medium priority task -> Low priority task
}
}案例 4:使用 ConcurrentHashMap 实现线程安全的计数器
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
class Counter {
private ConcurrentMap<String, Integer> counters = new ConcurrentHashMap<>();
public void increment(String key) {
counters.merge(key, 1, Integer::sum);
}
public int get(String key) {
return counters.getOrDefault(key, 0);
}
public void printAll() {
counters.forEach((k, v) -> System.out.println(k + ": " + v));
}
}
public class CounterExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
// 模拟多线程环境
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment("key");
}
};
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(task);
threads[i].start();
}
for (Thread thread : threads) {
thread.join();
}
counter.printAll(); // 输出: key: 10000
}
}面试要点
1. ArrayList 和 LinkedList 的区别
| 特性 | ArrayList | LinkedList |
|---|---|---|
| 底层实现 | 动态数组 | 双向链表 |
| 随机访问 | O(1) | O(n) |
| 插入删除 | O(n)(需要移动元素) | O(1)(只需修改指针) |
| 内存占用 | 较低 | 较高(存储指针) |
| 缓存友好性 | 好 | 差 |
| 适用场景 | 频繁查询、随机访问 | 频繁插入、删除 |
2. HashMap 的底层实现原理
JDK 1.8 中 HashMap 的底层实现:
- 数据结构:数组 + 链表 + 红黑树
- 哈希计算:
hash(key) ^ (hash(key) >>> 16) - 索引计算:
(n - 1) & hash - 扩容机制:当元素数量 > 容量 * 负载因子时,扩容为原来的 2 倍
- 链表转红黑树:当链表长度 > 8 且数组长度 >= 64 时
- 红黑树转链表:当红黑树节点数 < 6 时
3. ConcurrentHashMap 的实现原理
JDK 1.7:
- 使用分段锁(Segment)
- 每个 Segment 继承
ReentrantLock
JDK 1.8:
- 使用 CAS +
synchronized - 锁粒度更细(锁单个链表头节点)
- 性能更好
4. HashSet 如何保证元素不重复
- 调用元素的
hashCode()方法计算哈希值 - 如果哈希值不同,直接添加
- 如果哈希值相同,调用
equals()方法比较 - 如果
equals()返回 true,则认为是重复元素,不添加
5. 为什么重写 equals() 必须重写 hashCode()
因为如果两个对象 equals() 返回 true,它们的 hashCode() 必须相同。如果不重写 hashCode(),可能导致两个相等的对象哈希值不同,从而在 HashSet 或 HashMap 中出现重复。
6. fail-fast 和 fail-safe 的区别
- fail-fast:迭代器在遍历时直接访问集合内容,如果在遍历过程中集合结构发生变化(非迭代器自身方法),会抛出
ConcurrentModificationException。如ArrayList、HashMap的迭代器。 - fail-safe:迭代器在遍历时访问集合的副本,因此在遍历过程中集合结构的变化不会影响迭代器。如
CopyOnWriteArrayList、ConcurrentHashMap的迭代器。
7. 如何让 ArrayList 线程安全
- 使用
Collections.synchronizedList() - 使用
CopyOnWriteArrayList - 使用
synchronized关键字手动同步
8. TreeMap 和 HashMap 的区别
| 特性 | HashMap | TreeMap |
|---|---|---|
| 底层实现 | 哈希表 | 红黑树 |
| 元素顺序 | 无序 | 有序(按键排序) |
| 查找性能 | O(1) | O(log n) |
| null 键 | 允许一个 | 不允许 |
| 适用场景 | 需要快速查找 | 需要键有序 |
9. PriorityQueue 的实现原理
- 底层使用数组实现的小顶堆(默认)或大顶堆
- 插入元素时,从底部向上调整堆(上浮)
- 删除元素时,从顶部向下调整堆(下沉)
- 不允许 null 元素
- 非线程安全,线程安全版本为
PriorityBlockingQueue
10. 如何选择合适的集合
根据需求选择:
- 需要快速查找:
HashMap、HashSet - 需要有序:
TreeMap、TreeSet、LinkedHashMap、LinkedHashSet - 需要队列功能:
LinkedList、ArrayDeque、PriorityQueue - 需要线程安全:
ConcurrentHashMap、CopyOnWriteArrayList - 需要频繁插入删除:
LinkedList - 需要频繁随机访问:
ArrayList
总结
Java 集合框架提供了丰富的数据结构,用于存储和操作对象集合。选择合适的集合类型对于编写高效、可维护的代码至关重要。
关键要点
-
List 集合:
- 有序集合,允许重复元素
ArrayList适合随机访问,LinkedList适合频繁插入和删除CopyOnWriteArrayList适合读多写少的并发场景
-
Queue 队列:
- 先进先出(FIFO)的数据结构
LinkedList可作为队列使用,ArrayDeque性能更好PriorityQueue按优先级排序
-
Set 集合:
- 不包含重复元素的集合
HashSet提供快速查找,LinkedHashSet维护插入顺序,TreeSet维护排序顺序- 自定义对象作为元素时,必须重写
equals()和hashCode()方法
-
Map 集合:
- 存储键值对的集合
HashMap提供快速查找,LinkedHashMap维护插入顺序,TreeMap维护排序顺序- 自定义对象作为键时,必须重写
equals()和hashCode()方法
-
迭代器:
- 提供遍历集合的标准方式
- 使用迭代器的
remove()方法安全删除元素 - 增强 for 循环底层使用迭代器
-
线程安全:
- 使用并发集合(如
ConcurrentHashMap)或同步包装器 - 根据读写比例选择合适的线程安全集合
- 使用并发集合(如
最佳实践
- 选择合适的集合类型:根据具体需求选择最合适的集合实现
- 使用接口编程:尽量使用接口类型(如
List、Set、Map)声明变量,而不是具体实现类 - 重写必要的方法:自定义对象作为集合元素或键时,正确重写
equals()和hashCode()方法 - 考虑线程安全:多线程环境下,选择线程安全的集合实现或使用同步机制
- 利用泛型:使用泛型提高代码的类型安全性和可读性
- 使用 Stream API:在 Java 8+ 环境中,充分利用 Stream API 简化集合操作
- 指定初始容量:如果知道元素数量,建议在创建集合时指定初始容量,避免多次扩容
- 注意迭代器的并发修改异常:在迭代过程中不要通过集合方法修改集合
通过掌握这些集合类的特性和使用方法,可以编写出更加高效、灵活和可维护的 Java 代码。
版本差异(旧版 → Java 21)
| 特性 | 旧版(Java 8) | Java 9/10/16/21 |
|---|---|---|
| 不可变集合工厂 | Collections.unmodifiableXxx 包装可变集合 | List.of/Set.of/Map.of(Java 9)、List.copyOf(Java 10)真正不可变 |
| 迭代删除 | 手动 Iterator.remove() | Collection.removeIf(Java 8) |
| 空集合 | Collections.emptyList() | List.of() 等工厂方法(Java 9) |
| 顺序遍历 | forEach(Java 8) | Stream.toList()(Java 16)不可变收集 |
| 序列化安全 | 普通集合 | record 天然不可变(Java 16+),集合中承载 record 更安全 |