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 核心接口

核心思想:接口与实现分离
JCF (Java Collections Framework)
├── Collection [基础容器接口]
│   ├── List [有序、可重复]
│   │
│   ├── Set [无序、不可重复]
│   │
│   └── Queue [队列]
│
└── Map [键值对映射,与Collection平行]
    │
    ├── SortedMap [按键自动排序]
    │   └── NavigableMap [扩展导航方法]
    │
    └── ConcurrentMap [线程安全]
                    

JCF 关键实现类

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 选型速查表

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