Java 容器框架 (JCF)
核心精讲与实践
高性能、易于使用的数据结构工具箱
📦
→
高效数据结构
常规结构痛点
场景:我们要开发一个简单的学生管理系统,需要保存全班同学的名单。
😰 使用数组 String[]
// 容量固定,操作复杂
String[] students = new String[50];
students[0] = "张三";
students[1] = "李四";
// 需要手动管理大小和索引
🤔 痛点分析:
- 容量问题:如果只有30人,浪费20个空间;第51个学生怎么办?
- 增删问题:删除第2个学生,需手动移动后续所有元素
- 查找问题:检查"王五"是否存在,必须从头遍历(O(n))
- 功能问题:需要"不重复"名单?数组无能为力
😍 使用容器框架
// 动态大小,操作简单
List<String> students = new ArrayList<>();
students.add("张三");
students.add("李四");
// 自动管理大小,丰富的操作方法
✨ 容器框架的优势
🚀
- 自动扩容:不用担心容量问题
- 高效增删:
add(),remove()一行代码 - 查找性能:Set/Map 平均 O(1),List.contains O(n)
- 功能丰富:Set(去重)、Map(键值对)等
JCF 架构设计
核心思想:接口与实现分离
JCF 核心接口
核心思想:接口与实现分离
JCF (Java Collections Framework) ├── Collection [基础容器接口] │ ├── List [有序、可重复] │ │ │ ├── Set [无序、不可重复] │ │ │ └── Queue [队列] │ └── Map [键值对映射,与Collection平行] │ ├── SortedMap [按键自动排序] │ └── NavigableMap [扩展导航方法] │ └── ConcurrentMap [线程安全]
JCF 关键实现类
JCF (Java Collections Framework) ├── Collection [基础容器接口] │ ├── List [有序、可重复] │ │ ├── ArrayList 🌟[首选] │ │ │ └── (动态数组+自动扩容) │ │ ├── LinkedList [频繁增删] │ │ │ └── (双向链表+prev/next指针) │ │ └── Vector [遗留类] │ │ └── (同步数组+synchronized方法) │ │ │ ├── Set [不可重复] │ │ ├── HashSet 🌟[首选-无序] │ │ │ └── (哈希表+HashMap实现) │ │ ├── TreeSet [自动排序] │ │ │ └── (红黑树+TreeMap实现) │ │ ├── LinkedHashSet [保持插入顺序] │ │ │ └── (哈希表+双向链表) │ │ └── EnumSet [枚举专用] │ │ └── (位向量+高性能) │ │ │ └── Queue [队列-FIFO] │ ├── Deque [双端队列] │ │ ├── ArrayDeque 🌟[首选] │ │ │ └── (循环数组+双端队列) │ │ └── LinkedList [也实现List] │ │ └── (双向链表+双端操作) │ │ │ ├── PriorityQueue [优先级队列] │ │ └── (堆结构-默认小顶堆) │ │ │ └── (遗留队列) │ └── Stack [LIFO-遗留类] │ └── (继承Vector+push/pop) │ └── Map [键值对映射,与Collection平行] ├── SortedMap [按键自动排序] │ └── NavigableMap [扩展导航方法] │ └── TreeMap 🌟[唯一实现] │ └── (红黑树+自平衡BST) │ └── (直接Map实现) ├── HashMap 🌟[首选] │ └── (哈希表+数组+链表+红黑树) ├── LinkedHashMap [保持插入顺序/LRU] │ └── (HashMap+双向链表+LRU支持) ├── Hashtable [遗留类-线程安全] │ └── (同步哈希表+synchronized方法) ├── Properties [配置文件] │ └── (继承Hashtable+String类型) ├── EnumMap [枚举键专用] │ └── (数组实现+高性能) └── WeakHashMap [弱引用] └── (弱引用键+GC自动清理)
实战案例:学生管理
🎯 场景1:存储班级学生名单,按录入顺序排列
🤔 分析:
- 按录入顺序 → 有序
- 可能有重名 → 可重复
- 有序 + 可重复 → List
- 常用操作:点名(遍历)、查看第N个学生(get) → ArrayList查询最快
// 1. 创建名单
List<String> studentList = new ArrayList<>();
// 2. 添加学生
studentList.add("张三");
studentList.add("李四");
studentList.add("王五");
studentList.add("张三"); // List允许重复
// 3. 按索引获取
System.out.println("第2个学生是: " + studentList.get(1));
// 4. 遍历(点名)
for(String student : studentList) {
System.out.println(student);
}
📚 场景2:存储已选课程名称,不能重复
🤔 分析:
- 课程名称 → String
- 不能重复 → 唯一
- 唯一 → Set
- 只关心"是否选了某门课"(contains),性能优先 → HashSet
// 1. 创建课程池
Set<String> courseSet = new HashSet<>();
// 2. 添加课程
courseSet.add("Java程序设计");
courseSet.add("计算机网络");
courseSet.add("Java程序设计"); // 重复被自动忽略
// 3. 检查
System.out.println(courseSet.size()); // 输出: 2
System.out.println("是否选了Java? " + courseSet.contains("Java程序设计"));
🎓 场景3:存储学号和对应的GPA
🤔 分析:
- 学号(Integer) + GPA(Double) → 键值对
- 学号唯一,用来查找GPA → Key唯一
- Key-Value → Map
- 查找速度最快,不关心顺序 → HashMap
// 1. 创建成绩单
Map<Integer, Double> gpaMap = new HashMap<>();
// 2. 录入成绩
gpaMap.put(1001, 3.8);
gpaMap.put(1002, 3.5);
gpaMap.put(1003, 4.0);
// 3. 查询成绩
Double gpa = gpaMap.get(1002);
System.out.println("1002号的GPA是: " + gpa);
// 4. 遍历
for(Map.Entry<Integer, Double> entry : gpaMap.entrySet()) {
System.out.println("学号: " + entry.getKey() + ", GPA: " + entry.getValue());
}
JCF 选型速查表
黄金选型法则
| 你的需求 | 接口选择 | 默认实现 | 时间复杂度 |
|---|---|---|---|
| 有序、可重复的列表 | List<T> |
ArrayList |
get(): O(1), add(): 均摊 O(1) |
| 唯一元素,高性能 | Set<T> |
HashSet |
add(): O(1), contains(): O(1) |
| 唯一元素,自动排序 | Set<T> |
TreeSet |
add(): O(log n), 有序 |
| 键值对,高性能 | Map<K,V> |
HashMap |
get(): O(1), put(): O(1) |
| 键值对,Key自动排序 | Map<K,V> |
TreeMap |
get(): O(log n), Key有序 |
🎯 经验法则
🔍 需要快速随机访问?
选择:ArrayList
场景:频繁通过索引访问元素
🔄 频繁插入删除?
选择:LinkedList
场景:队列、栈、频繁修改的列表
🚫 不允许重复元素?
选择:HashSet
场景:去重、快速查找
📊 需要自动排序?
选择:TreeSet/TreeMap
场景:有序数据、范围查询
🗝️ 需要键值对映射?
选择:HashMap
场景:缓存、索引、计数
⭐ 需要优先级处理?
选择:PriorityQueue
场景:任务调度、算法实现
永远的经验
🤔 不确定用什么?
- • 需要列表?用 ArrayList
- • 需要去重?用 HashSet
- • 需要键值对?用 HashMap
容器CRUD速查
一图掌握 List、Set、Map 的增删改查核心方法与差异
| 接口 | ➕ 增 (Create) | ❌ 删 (Delete) | 🔄 改 (Update) | 🔍 查 (Read) |
|---|---|---|---|---|
|
List 有序 可重复 |
add(E)add(int, E)addAll(Coll)
|
remove(int)remove(Obj)removeIf(Pred)
|
set(int, E)replaceAll(Op)sort(Comp)
|
get(int)indexOf(Obj)subList(start, end)
|
|
Set 无序 唯一 |
add(E)addAll(Coll)重复返回false |
remove(Obj)removeIf(Pred)removeAll(Coll)
|
无直接修改 需先删后加 |
contains(Obj)iterator()stream()
|
|
Map 键值对 Key唯一 |
put(K, V)putAll(Map)putIfAbsent(K,V)
|
remove(K)remove(K, V)
|
replace(K, V)compute(K, Func)merge(K, V, Func)
|
get(K)getOrDefault(K,V)containsKey(K)
|
💡 核心要点与性能提示
⚡ 性能关键
- ArrayList: 随机访问
get(i)为 O(1),插入/删除 O(n)。 - HashSet/HashMap: 增删查平均 O(1),依赖 hashCode。
- LinkedList: 头尾操作 O(1),随机访问 O(n)。
🛠️ 实用技巧
- 空指针保护: 优先用
getOrDefault替代get。 - 批量操作:
addAll/removeAll比循环更高效。 - 集合转换:
new ArrayList<>(set)可快速去重转列表。
容器遍历方式
一站式掌握 List、Set、Map 的高效遍历与安全操作
📊 遍历方式性能与场景对比
| 方式 | 适用场景 | 特点 |
|---|---|---|
| 增强for | 只读遍历 (List/Set) | 语法最简洁,无法删除 |
| Iterator | 需要删除元素 | 唯一安全的遍历删除方式 |
| Stream API | 复杂处理 (过滤/映射) | 函数式风格,支持并行 |
| entrySet | Map 遍历 (推荐) | 同时获取Key和Value,最高效 |
| for(i) | List (需索引) | ArrayList中最快,LinkedList中最慢 |
⚠️ 关键陷阱:ConcurrentModificationException
永远不要在 foreach 循环中直接调用 list.remove()!
✅ 正确做法:使用 Iterator.remove() 或 Java 8+ removeIf()。
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));
// ❌ 错误做法:会抛出 ConcurrentModificationException
try {
for (String item : list) {
if (item.equals("B")) {
list.remove(item); // 在 foreach 中直接修改集合
}
}
} catch (ConcurrentModificationException e) {
System.out.println("捕获异常: " + e.getClass().getSimpleName());
}
// ✅ 正确做法 1:使用 Iterator
List<String> list1 = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));
Iterator<String> iterator = list1.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (item.equals("B")) {
iterator.remove(); // 安全删除
}
}
// ✅ 正确做法 2:使用 removeIf()(Java 8+)
List<String> list2 = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));
list2.removeIf(item -> item.equals("B")); // 简洁安全
💻 核心代码范例
List / Set 遍历
// 1. 增强for (推荐只读)
for (String s : list) { ... }
// 2. Iterator (安全删除)
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if ("target".equals(it.next())) {
it.remove(); // ✅ 安全
}
}
// 3. Stream (过滤+处理)
list.stream()
.filter(s -> s.length() > 3)
.forEach(System.out::println);
Map 遍历
// 1. entrySet (推荐高效)
for (Map.Entry<K,V> entry : map.entrySet()) {
K key = entry.getKey();
V val = entry.getValue();
}
// 2. forEach (Java 8+ 简洁)
map.forEach((k, v) -> {
System.out.println(k + "=" + v);
});
集合间的操作与转换
灵活运用集合间的转换和操作,能让代码更简洁高效
🔄 集合转换操作
🔄 数组 ↔ 集合
// 数组 → 集合
String[] array = {"张三", "李四", "王五"};
// 方式1: Arrays.asList()(长度固定)
List<String> list1 = Arrays.asList(array);
// list1.add("新元素"); // 抛出异常
// 方式2: new ArrayList<>()(可修改)
List<String> list2 = new ArrayList<>(Arrays.asList(array));
// 集合 → 数组
List<String> list = Arrays.asList("A", "B", "C");
// 方式1: toArray(T[] array)
String[] array1 = list.toArray(new String[0]);
// 方式2: toArray()(返回Object[])
Object[] array2 = list.toArray();
🔄 List ↔ Set
List<String> list = Arrays.asList("张三", "李四", "张三", "王五");
// List → Set(自动去重)
Set<String> set = new HashSet<>(list);
System.out.println(set); // [张三, 李四, 王五]
// Set → List
List<String> uniqueList = new ArrayList<>(set);
// 实际应用:快速去重
public static <T> List<T> removeDuplicates(List<T> list) {
return new ArrayList<>(new HashSet<>(list));
}
// 使用示例
List<String> names = Arrays.asList("张三", "李四", "张三");
List<String> uniqueNames = removeDuplicates(names);
🔢 集合的数学运算
// 并集(Union)
Set<String> set1 = new HashSet<>(Arrays.asList("A", "B", "C"));
Set<String> set2 = new HashSet<>(Arrays.asList("C", "D", "E"));
Set<String> union = new HashSet<>(set1);
union.addAll(set2); // [A, B, C, D, E]
// 交集(Intersection)
Set<String> intersection = new HashSet<>(set1);
intersection.retainAll(set2); // [C]
// 差集(Difference)
Set<String> difference = new HashSet<>(set1);
difference.removeAll(set2); // [A, B]
// 实际应用:求两个课程的共同学生
public static <T> Set<T> getIntersection(Set<T> set1, Set<T> set2) {
Set<T> result = new HashSet<>(set1);
result.retainAll(set2);
return result;
}
🛠️ Collections 工具类常用方法
📊 排序与打乱
List<Integer> numbers = new ArrayList<>(
Arrays.asList(3, 1, 4, 1, 5, 9));
// 排序(升序)
Collections.sort(numbers); // [1, 1, 3, 4, 5, 9]
// 降序排序
Collections.sort(numbers, Collections.reverseOrder());
// 打乱顺序
Collections.shuffle(numbers);
// 反转
Collections.reverse(numbers);
// 旋转(将后面元素移到前面)
Collections.rotate(numbers, 2);
🔍 查找与统计
List<String> names = Arrays.asList(
"张三", "李四", "王五", "张三");
// 查找最大最小值
String max = Collections.max(names);
String min = Collections.min(names);
// 二分查找(需要先排序)
List<Integer> sorted = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
int index = Collections.binarySearch(sorted, 3); // 2
// 统计出现次数
int count = Collections.frequency(names, "张三"); // 2
// 替换所有元素
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
Collections.fill(list, "默认值");
🔧 创建特殊集合
// 不可变集合
List<String> immutable = Collections.unmodifiableList(
new ArrayList<>(Arrays.asList("A", "B")));
// 单元素集合
List<String> single = Collections.singletonList("OnlyOne");
// 空集合
List<String> empty = Collections.emptyList();
// 同步集合(线程安全)
List<String> syncList = Collections.synchronizedList(
new ArrayList<>());
💡 实际应用案例
// 案例1:洗牌算法
public static <T> void shuffleList(List<T> list) {
Collections.shuffle(list, new Random());
}
// 案例2:查找Top N元素
public static List<Integer> getTopN(List<Integer> list, int n) {
List<Integer> copy = new ArrayList<>(list);
Collections.sort(copy, Collections.reverseOrder());
return copy.subList(0, Math.min(n, copy.size()));
}
💡 实战技巧总结
🔄 快速去重
List → Set → List
new ArrayList<>(new HashSet<>(list))
📊 排序操作
Collections.sort(list)
Collections.reverse(list)
🔍 查找操作
Collections.frequency(list, item)
Collections.binarySearch(list, item)
进阶:线程安全与最佳实践
高并发场景下的集合选择与生产环境开发规范
🔒 线程安全指南
原则:普通集合(ArrayList/HashMap)非线程安全。多线程环境必须使用并发集合。
✅ 推荐方案 (JUC)
// Map: 高性能并发读写
Map<K,V> map = new ConcurrentHashMap<>();
// List: 读多写少 (写时复制)
List<T> list = new CopyOnWriteArrayList<>();
// Queue: 高性能非阻塞队列
Queue<T> queue = new ConcurrentLinkedQueue<>();
🚫 避免使用 (遗留类)
// 性能差,所有操作加锁
Vector<T> v = new Vector<>();
Hashtable<K,V> t = new Hashtable<>();
// Collections.synchronizedList(...)
�️ 开发最佳实践
-
面向接口编程
List<String> list = new ArrayList<>(); -
设置初始容量
预估大小,避免频繁扩容 (Resize代价大)。new HashMap<>(16); -
使用不可变集合 (Java 9+)
List.of("A", "B"),Map.of("K", "V")
(更安全,占用内存更少) -
判空处理
返回空集合Collections.emptyList()而非 null。
使用CollectionUtils.isEmpty(list)。 -
Map 遍历
优先使用entrySet()或forEach()。
ArrayList 深度解析
了解ArrayList的内部实现原理,写出更高效的代码
🏗️ 动态数组原理
📊 ArrayList vs 普通数组
// 普通数组 - 容量固定
String[] array = new String[5];
// array[6] = "越界"; // 数组越界异常
// ArrayList - 自动扩容
List<String> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add("元素" + i); // 自动扩容
}
// ArrayList内部维护一个Object[]数组
// transient Object[] elementData;
// private int size;
⚡ 扩容机制详解
// ArrayList扩容源码分析
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍扩容
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
elementData = Arrays.copyOf(elementData, newCapacity);
}
// 扩容过程:
// 1. 创建新数组(1.5倍大小)
// 2. 复制旧数组到新数组
// 3. 丢弃旧数组
💡 扩容的性能影响
ArrayList的默认初始容量是10,每次扩容时创建1.5倍大小的新数组并复制所有元素。频繁扩容会严重影响性能。
最佳实践:在知道大致元素数量时,使用 new ArrayList<>(expectedSize)
预设容量。
🎯 适用场景分析
✅ 适合ArrayList的场景
- 频繁随机访问:通过索引获取元素
- 顺序遍历:for循环或迭代器遍历
- 读多写少:查询操作远多于增删操作
- 元素数量相对稳定:不会频繁大幅变化
❌ 不适合ArrayList的场景
- 频繁插入删除:特别是在列表中间
- 需要线程安全:多线程环境读写
- 元素数量变化极大:频繁扩容影响性能
- 大量唯一性检查:contains操作频繁
🆚 ArrayList vs 其他List实现
⚡ ArrayList vs LinkedList
// 性能对比测试
public class ListPerformanceTest {
public static void main(String[] args) {
final int COUNT = 100000;
// 测试随机访问
List<Integer> arrayList = new ArrayList<>();
List<Integer> linkedList = new LinkedList<>();
// 填充数据
for (int i = 0; i < COUNT; i++) {
arrayList.add(i);
linkedList.add(i);
}
// 随机访问测试
long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
arrayList.get(new Random().nextInt(COUNT));
}
long arrayListTime = System.currentTimeMillis() - start;
start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
linkedList.get(new Random().nextInt(COUNT));
}
long linkedListTime = System.currentTimeMillis() - start;
System.out.println("ArrayList随机访问: " + arrayListTime + "ms");
System.out.println("LinkedList随机访问: " + linkedListTime + "ms");
// 结果:ArrayList快数百倍
}
}
💡 线程安全提醒
ArrayList是非线程安全的。多线程环境下需要使用线程安全的替代方案:
- 并发集合:CopyOnWriteArrayList(推荐)
- 同步包装:Collections.synchronizedList()
- 避免使用:Vector(性能较差的遗留类)
详细线程安全指南请参考"JCF选型速查表"章节。
🎯 ArrayList 最佳实践
✅ 推荐做法
- 预设初始容量避免频繁扩容
- 使用增强for循环遍历
- 批量操作使用addAll方法
- 需要线程安全时使用包装类
- 大数组使用trimToSize()回收内存
❌ 避免做法
- 在循环中逐个添加元素到空列表
- 频繁在列表中间插入删除
- 多线程环境下直接使用ArrayList
- 用contains检查大量元素
- 存储过多null元素
HashMap 工作原理深度解析
掌握HashMap的内部机制,理解哈希表的精髓
🏗️ HashMap 内部结构
📊 基本数据结构
// HashMap核心字段
public class HashMap<K,V> {
// 哈希桶数组
transient Node<K,V>[] table;
// 键值对数量
transient int size;
// 扩容阈值 = 容量 * 加载因子
int threshold;
// 加载因子(默认0.75)
final float loadFactor;
// Node节点结构
static class Node<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
}
🔢 哈希计算过程
// 1. 计算Key的哈希值
static final int hash(Object key) {
int h;
// 高16位异或低16位,减少哈希冲突
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// 2. 计算数组索引位置
// n = table.length 必须是2的幂
int index = (n - 1) & hash;
// 示例:容量16,hash值为5
// n = 16, n-1 = 15 (二进制1111)
// hash = 5 (二进制0101)
// index = 15 & 5 = 5
💡 为什么容量必须是2的幂?
为了让哈希分布更均匀,Java 8使用(n - 1) & hash来计算索引。当n是2的幂时,n-1的二进制全为1,这样hash的任何一位都能影响结果,使哈希分布更均匀。
⚡ 哈希冲突处理
🔗 Java 7: 链表法
// 哈希冲突时使用链表连接
// 时间复杂度:O(n)
// 插入过程:
// 1. 计算hash和索引
// 2. 遍历链表检查是否已存在
// 3. 不存在则在链表头部插入
// 4. 存在则更新value
// 查找过程:
// 1. 计算hash和索引
// 2. 遍历链表查找目标key
// 3. 找到返回value,未找到返回null
// 缺点:链表过长时性能急剧下降
🌳 Java 8: 链表 + 红黑树
// 链表长度超过8时转换为红黑树
static final int TREEIFY_THRESHOLD = 8;
// 红黑树节点
static final class TreeNode<K,V> extends Node<K,V> {
TreeNode<K,V> parent;
TreeNode<K,V> left;
TreeNode<K,V> right;
boolean red;
}
// 时间复杂度优化:
// - 链表:O(n)
// - 红黑树:O(log n)
// 转换条件:
// 1. 链表长度 >= 8
// 2. 桶数组容量 >= 64
// 扩容机制:当 size >= threshold 时扩容
// threshold = capacity * loadFactor
// 默认:初始容量16,加载因子0.75,扩容阈值12
// 扩容过程:
// 1. 创建新的数组,容量翻倍
// 2. 重新计算所有元素的hash和位置
// 3. 将元素迁移到新数组
// 4. 红黑树分裂(如果存在)
// 性能影响:扩容是O(n)操作,应尽量避免
🎯 HashMap 性能优化
⚠️ 常见性能问题
// 1. 哈希冲突过多
class BadKey {
private int value;
// ❌ 没有重写hashCode,所有对象hash相同
// public int hashCode() { return 1; }
// ❌ 没有重写equals,无法正确查找
}
// 2. 频繁扩容
Map<String, String> map = new HashMap<>(); // 默认容量16
for (int i = 0; i < 10000; i++) {
map.put("key" + i, "value" + i); // 多次扩容
}
// 3. 使用Integer作为Key造成大量装箱
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 1000000; i++) {
map.put(i, "value"); // 频繁装箱
}
✅ 优化方案
// 1. 正确实现hashCode和equals
class GoodKey {
private int value;
// ✅ 好的hashCode实现
@Override
public int hashCode() {
return Integer.hashCode(value);
}
// ✅ 正确的equals实现
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof GoodKey)) return false;
return value == ((GoodKey) obj).value;
}
}
// 2. 预设初始容量
Map<String, String> map = new HashMap<>(10000); // 预设容量
// 3. 使用基本类型集合(第三方库)
// 或使用Int2ObjectOpenHashMap等
🔧 实际应用技巧
⚖️ 加载因子选择
// 默认加载因子0.75的权衡:
// - 0.75:时间和空间的平衡
// - 更大:减少扩容但增加冲突
// - 更小:减少冲突但增加扩容
// 内存敏感场景:0.5
Map<K, V> map1 = new HashMap<>(capacity, 0.5f);
// 性能优先场景:0.9
Map<K, V> map2 = new HashMap<>(capacity, 0.9f);
// 一般场景:使用默认0.75
Map<K, V> map3 = new HashMap<>(capacity);
💡 线程安全提醒
HashMap是非线程安全的。多线程环境下必须使用线程安全的替代方案:
- 强烈推荐:ConcurrentHashMap(高性能并发Map)
- 备选方案:Collections.synchronizedMap()(性能较低)
- 只读场景:Collections.unmodifiableMap()
详细线程安全指南和性能对比请参考"JCF选型速查表"章节。
🎯 HashMap 最佳实践总结
✅ 推荐做法
- 合理设置初始容量,避免频繁扩容
- 确保Key类正确实现hashCode和equals
- 使用不可变对象作为Key
- 多线程环境使用ConcurrentHashMap
- 考虑使用Java 8的computeIfAbsent等新方法
❌ 避免做法
- 使用可变对象作为Key
- 忘记重写hashCode和equals
- 在HashMap中存储null作为Key(虽然允许但不推荐)
- 频繁使用containsKey + get(改用getOrDefault)
- 在循环中向HashMap添加大量元素
* 排序与比较机制详解
掌握Comparable和Comparator,实现灵活的元素排序
🔄 两种比较接口
🔤 Comparable:自然排序
// 定义在类内部,实现自然排序规则
public class Student implements Comparable<Student> {
private String name;
private int age;
private double gpa;
public Student(String name, int age, double gpa) {
this.name = name;
this.age = age;
this.gpa = gpa;
}
// ✅ 实现compareTo方法
@Override
public int compareTo(Student other) {
// 按年龄升序排序
return Integer.compare(this.age, other.age);
// 其他比较方式:
// return this.name.compareTo(other.name); // 按姓名
// return Double.compare(this.gpa, other.gpa); // 按GPA
}
// getter和setter方法...
}
// 使用方式
List<Student> students = Arrays.asList(
new Student("张三", 20, 3.5),
new Student("李四", 18, 3.8),
new Student("王五", 22, 3.2)
);
Collections.sort(students); // 直接排序,使用自然排序规则
🛠️ Comparator:自定义排序
// 定义在外部,灵活的排序规则
public class StudentComparator {
// 按姓名排序
public static Comparator<Student> byName() {
return Comparator.comparing(Student::getName);
}
// 按GPA降序排序
public static Comparator<Student> byGpaDescending() {
return Comparator.comparing(Student::getGpa)
.reversed();
}
// 多条件排序:先按GPA降序,再按年龄升序
public static Comparator<Student> complexSorting() {
return Comparator.comparing(Student::getGpa)
.reversed()
.thenComparing(Student::getAge);
}
}
// 使用方式
List<Student> students = new ArrayList<>();
// 方式1: 使用Comparator排序
Collections.sort(students, StudentComparator.byName());
// 方式2: Java 8 Lambda表达式
students.sort(Comparator.comparing(Student::getAge));
// 方式3: 复杂排序
students.sort(StudentComparator.complexSorting());
🌳 TreeSet和TreeMap的排序
🌱 TreeSet自然排序
// 使用Comparable的自然排序
public class Person implements Comparable<Person> {
private String name;
private int age;
@Override
public int compareTo(Person other) {
// 按姓名排序
return this.name.compareTo(other.name);
}
}
// TreeSet自动排序
Set<Person> people = new TreeSet<>();
people.add(new Person("张三", 20));
people.add(new Person("李四", 18));
people.add(new Person("王五", 22));
// 输出是按姓名排序的
people.forEach(System.out::println);
// ❌ 如果Person没有实现Comparable
// ClassCastException异常
🎯 TreeSet自定义排序
// 方式1: 构造时传入Comparator
Set<Student> students = new TreeSet<>(
Comparator.comparing(Student::getGpa)
.reversed() // GPA降序
.thenComparing(Student::getAge) // GPA相同时按年龄
);
// 方式2: 使用匿名内部类
Set<Student> students2 = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
return s1.getAge() - s2.getAge();
}
});
// 方式3: 自定义Comparator类
class StudentAgeComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
return Integer.compare(s1.getAge(), s2.getAge());
}
}
Set<Student> students3 = new TreeSet<>(new StudentAgeComparator());
🔧 Comparator 高级用法
// Java 8 Comparator的强大功能
// 1. 基本比较
Comparator<Student> byAge = Comparator.comparing(Student::getAge);
// 2. 逆序
Comparator<Student> byAgeDesc = Comparator.comparing(Student::getAge)
.reversed();
// 3. 多条件排序
Comparator<Student> complex = Comparator.comparing(Student::getGpa)
.reversed()
.thenComparing(Student::getAge)
.thenComparing(Student::getName);
// 4. 处理null值
Comparator<Student> nullSafe = Comparator.comparing(Student::getName,
Comparator.nullsFirst(naturalOrder()));
// 5. 自定义比较逻辑
Comparator<Student> custom = Comparator.comparing(student -> {
if (student.getGpa() >= 4.0) return "A";
if (student.getGpa() >= 3.0) return "B";
return "C";
});
// 实际应用示例
List<Student> students = getStudentList();
// 复杂排序:先按成绩等级,再按具体成绩,最后按姓名
students.sort(
Comparator.comparing((Student s) -> {
if (s.getGpa() >= 4.0) return 3;
if (s.getGpa() >= 3.5) return 2;
if (s.getGpa() >= 3.0) return 1;
return 0;
}).reversed()
.thenComparing(Student::getGpa).reversed()
.thenComparing(Student::getName)
);
📚 实战应用案例
🎓 案例1:学生成绩排序系统
public class StudentSorter {
public static void sortStudents(List<Student> students, String criteria) {
switch (criteria) {
case "name":
students.sort(Comparator.comparing(Student::getName));
break;
case "age":
students.sort(Comparator.comparing(Student::getAge));
break;
case "gpa":
students.sort(Comparator.comparing(Student::getGpa).reversed());
break;
case "comprehensive":
students.sort(
Comparator.comparing(Student::getGpa).reversed()
.thenComparing(Student::getAge)
.thenComparing(Student::getName)
);
break;
}
}
}
🗺️ 案例2:Map按键值排序
public class MapSorter {
// 按Key排序
public static <K, V> Map<K, V> sortByKey(Map<K, V> map, Comparator<K> comparator) {
return map.entrySet().stream()
.sorted(Map.Entry.comparingByKey(comparator))
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(oldValue, newValue) -> oldValue,
LinkedHashMap::new
));
}
// 按Value排序
public static <K, V extends Comparable<V>> Map<K, V> sortByValue(Map<K, V> map) {
return map.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(oldValue, newValue) -> oldValue,
LinkedHashMap::new
));
}
}
💡 排序最佳实践总结
📝 何时使用Comparable
- 类有明确的"自然排序"规则
- 排序规则在类设计时就确定
- 需要多次使用相同排序规则
- TreeSet/TreeMap的默认排序
🛠️ 何时使用Comparator
- 需要多种不同的排序规则
- 类无法修改(如第三方库)
- 需要动态改变排序规则
- 复杂的排序逻辑
// 记忆口诀:
// Comparable在类里面,是"自然排序"
// Comparator在类外面,是"灵活排序"
// 两者配合使用,排序无往不利!
1 / X