0
0

Java 入门指南:第三章 - Java 集合框架完全指南

2026-09-07
文章摘要
|

承接前两章的基础语法和面向对象内容,本章将深入讲解 Java 集合框架(Java Collections Framework)。集合是实际开发中使用频率最高的工具之一,掌握好集合框架,你的编程效率将提升一个档次。


第三章:Java 集合框架(Collections Framework)

3.1 集合框架概述

3.1.1 为什么需要集合框架?

在编程中,我们经常需要存储和操作一组数据。虽然数组可以做到,但它有以下限制:

  • 长度固定,不能动态扩容
  • 只能存储同一类型的数据
  • 操作方法有限(没有现成的排序、查找等)

集合框架(Collections Framework)就是为了解决这些问题而设计的统一架构。

3.1.2 集合框架的核心接口层次结构

Iterable (根接口)
    └── Collection (集合根接口)
            ├── List (有序、可重复)
            │       ├── ArrayList
            │       ├── LinkedList
            │       ├── Vector (线程安全)
            │       └── Stack
            ├── Set (无序、不可重复)
            │       ├── HashSet
            │       ├── LinkedHashSet
            │       └── TreeSet
            └── Queue (队列)
                    ├── LinkedList
                    ├── PriorityQueue
                    └── ArrayDeque

Map (独立的根接口,不是 Collection 的子接口)
    ├── HashMap
    ├── LinkedHashMap
    ├── TreeMap
    ├── Hashtable (线程安全)
    └── ConcurrentHashMap (并发优化)

3.1.3 集合框架的三大分类

分类 特点 主要实现类
List 有序、可重复、有索引 ArrayList, LinkedList, Vector
Set 无序、不可重复 HashSet, LinkedHashSet, TreeSet
Map 键值对(key-value)存储 HashMap, LinkedHashMap, TreeMap

3.2 Collection 接口:所有集合的根

3.2.1 Collection 的通用方法

import java.util.*;

public class CollectionBasics {
    public static void main(String[] args) {
        // 使用多态创建集合
        Collection<String> collection = new ArrayList<>();
        
        // 1. 添加元素
        collection.add("Java");
        collection.add("Python");
        collection.add("Go");
        System.out.println("添加后:" + collection); // [Java, Python, Go]
        
        // 2. 批量添加
        Collection<String> more = Arrays.asList("C++", "JavaScript");
        collection.addAll(more);
        System.out.println("批量添加后:" + collection);
        
        // 3. 删除元素
        collection.remove("Go");
        System.out.println("删除 Go 后:" + collection);
        
        // 4. 判断包含
        System.out.println("是否包含 Java?" + collection.contains("Java"));
        System.out.println("是否包含所有?" + collection.containsAll(more));
        
        // 5. 大小判断
        System.out.println("集合大小:" + collection.size());
        System.out.println("是否为空:" + collection.isEmpty());
        
        // 6. 转换为数组
        Object[] array = collection.toArray();
        String[] stringArray = collection.toArray(new String[0]);
        
        // 7. 清除所有元素
        collection.clear();
        System.out.println("清空后:" + collection);
    }
}

3.2.2 遍历集合的四种方式

public class CollectionTraversal {
    public static void main(String[] args) {
        List<String> fruits = Arrays.asList("苹果", "香蕉", "橙子", "葡萄");
        
        // 方式1:普通 for 循环(仅适用于 List)
        System.out.println("=== 普通 for 循环 ===");
        for (int i = 0; i < fruits.size(); i++) {
            System.out.println(fruits.get(i));
        }
        
        // 方式2:增强 for 循环(推荐)
        System.out.println("=== 增强 for 循环 ===");
        for (String fruit : fruits) {
            System.out.println(fruit);
        }
        
        // 方式3:迭代器(Iterator)
        System.out.println("=== 迭代器 ===");
        Iterator<String> iterator = fruits.iterator();
        while (iterator.hasNext()) {
            String fruit = iterator.next();
            System.out.println(fruit);
            // 可以在遍历时删除元素
            // iterator.remove();
        }
        
        // 方式4:Lambda 表达式(Java 8+)
        System.out.println("=== Lambda 表达式 ===");
        fruits.forEach(fruit -> System.out.println(fruit));
        
        // 方式4b:方法引用
        System.out.println("=== 方法引用 ===");
        fruits.forEach(System.out::println);
    }
}

3.3 List 接口详解

3.3.1 ArrayList:最常用的列表实现

特点

  • 底层是数组,查询快(O(1))
  • 增删慢(O(n)),特别是中间位置
  • 线程不安全
  • 支持随机访问(实现了 RandomAccess 接口)
public class ArrayListDemo {
    public static void main(String[] args) {
        // 创建 ArrayList
        ArrayList<String> list = new ArrayList<>();
        // 指定初始容量(避免扩容开销)
        ArrayList<String> listWithCapacity = new ArrayList<>(100);
        
        // 添加元素
        list.add("A");
        list.add("B");
        list.add("C");
        list.add(1, "X");  // 在索引 1 插入
        System.out.println(list); // [A, X, B, C]
        
        // 获取元素
        String element = list.get(2);
        System.out.println("索引2的元素:" + element); // B
        
        // 修改元素
        list.set(1, "Y");
        System.out.println("修改后:" + list); // [A, Y, B, C]
        
        // 删除元素(按索引)
        String removed = list.remove(0);
        System.out.println("删除的元素:" + removed);
        System.out.println("删除后:" + list); // [Y, B, C]
        
        // 删除元素(按对象)
        list.remove("B");
        System.out.println("删除 B 后:" + list); // [Y, C]
        
        // 查找元素索引
        list.add("C");
        System.out.println("C 首次出现位置:" + list.indexOf("C"));
        System.out.println("C 最后出现位置:" + list.lastIndexOf("C"));
        
        // 子列表(视图,修改会影响原列表)
        List<String> subList = list.subList(0, 1);
        System.out.println("子列表:" + subList);
        subList.set(0, "Z");
        System.out.println("修改子列表后原列表:" + list); // [Z, C, C]
        
        // 排序(自然顺序)
        list.sort(null);
        System.out.println("排序后:" + list); // [C, C, Z]
        
        // 自定义排序
        list.sort((a, b) -> b.compareTo(a)); // 降序
        System.out.println("降序:" + list); // [Z, C, C]
    }
}

3.3.2 深入理解 ArrayList 的扩容机制

public class ArrayListCapacity {
    public static void main(String[] args) throws Exception {
        // 使用反射查看底层数组大小
        ArrayList<Integer> list = new ArrayList<>();
        
        System.out.println("初始容量:10(默认)");
        System.out.println("实际大小:0");
        
        // 添加 11 个元素,触发扩容
        for (int i = 0; i < 11; i++) {
            list.add(i);
            System.out.println("添加第 " + (i+1) + " 个元素后,size = " + list.size());
        }
        
        // 扩容规则:新容量 = 旧容量 * 1.5
        // 10 -> 15 -> 22 -> 33 -> ...
        // 可以使用 ensureCapacity(int) 预分配容量
        
        // 手动缩减容量到当前实际大小
        list.trimToSize();
        System.out.println("执行 trimToSize() 后,底层数组大小变为 " + list.size());
    }
}

3.3.3 LinkedList:双向链表的实现

特点

  • 底层是双向链表
  • 增删快(O(1)),特别是首尾操作
  • 查询慢(O(n))
  • 实现了 Queue 和 Deque 接口,可以当队列使用
public class LinkedListDemo {
    public static void main(String[] args) {
        LinkedList<String> list = new LinkedList<>();
        
        // 作为 List 使用
        list.add("Java");
        list.add("Python");
        list.addFirst("Go");    // 插入到头部
        list.addLast("Rust");   // 插入到尾部
        System.out.println(list); // [Go, Java, Python, Rust]
        
        // 作为 Queue 使用(FIFO)
        Queue<String> queue = list;
        queue.offer("Swift");   // 入队
        System.out.println("队首元素:" + queue.peek());  // 查看不移除
        System.out.println("出队元素:" + queue.poll());  // 移除并返回
        System.out.println("出队后:" + queue);
        
        // 作为 Deque 使用(双端队列)
        Deque<String> deque = list;
        deque.addFirst("前端");
        deque.addLast("后端");
        System.out.println("双端队列:" + deque);
        
        // 特有方法
        System.out.println("获取第一个:" + list.getFirst());
        System.out.println("获取最后一个:" + list.getLast());
        System.out.println("移除第一个:" + list.removeFirst());
        System.out.println("移除最后一个:" + list.removeLast());
    }
}

3.3.4 ArrayList vs LinkedList 性能对比

public class ListPerformance {
    public static void main(String[] args) {
        final int SIZE = 100000;
        
        // 1. 插入性能测试
        long startTime = System.nanoTime();
        ArrayList<Integer> arrayList = new ArrayList<>();
        for (int i = 0; i < SIZE; i++) {
            arrayList.add(i);
        }
        long arrayListTime = System.nanoTime() - startTime;
        
        startTime = System.nanoTime();
        LinkedList<Integer> linkedList = new LinkedList<>();
        for (int i = 0; i < SIZE; i++) {
            linkedList.add(i);
        }
        long linkedListTime = System.nanoTime() - startTime;
        
        System.out.println("尾部插入耗时:");
        System.out.println("ArrayList: " + arrayListTime / 1_000_000 + " ms");
        System.out.println("LinkedList: " + linkedListTime / 1_000_000 + " ms");
        
        // 2. 中间插入测试
        startTime = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            arrayList.add(SIZE / 2, i);
        }
        arrayListTime = System.nanoTime() - startTime;
        
        startTime = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            linkedList.add(SIZE / 2, i);
        }
        linkedListTime = System.nanoTime() - startTime;
        
        System.out.println("\n中间插入 1000 次耗时:");
        System.out.println("ArrayList: " + arrayListTime / 1_000_000 + " ms");
        System.out.println("LinkedList: " + linkedListTime / 1_000_000 + " ms");
        
        // 3. 随机访问测试
        startTime = System.nanoTime();
        for (int i = 0; i < SIZE; i++) {
            arrayList.get(i);
        }
        arrayListTime = System.nanoTime() - startTime;
        
        startTime = System.nanoTime();
        for (int i = 0; i < SIZE; i++) {
            linkedList.get(i);
        }
        linkedListTime = System.nanoTime() - startTime;
        
        System.out.println("\n随机访问 " + SIZE + " 次耗时:");
        System.out.println("ArrayList: " + arrayListTime / 1_000_000 + " ms");
        System.out.println("LinkedList: " + linkedListTime / 1_000_000 + " ms");
    }
}

3.3.5 Vector 和 Stack(线程安全的遗留类)

public class VectorDemo {
    public static void main(String[] args) {
        // Vector:线程安全的 ArrayList(同步)
        Vector<String> vector = new Vector<>();
        vector.add("A");
        vector.add("B");
        vector.add("C");
        
        // Vector 特有的方法
        System.out.println("容量:" + vector.capacity()); // 默认 10
        System.out.println("大小:" + vector.size());
        
        // Stack:继承自 Vector,LIFO(后进先出)
        Stack<String> stack = new Stack<>();
        stack.push("第一层");
        stack.push("第二层");
        stack.push("第三层");
        
        System.out.println("栈顶元素:" + stack.peek()); // 查看不移除
        System.out.println("弹出元素:" + stack.pop());   // 弹出
        System.out.println("弹出后栈顶:" + stack.peek());
        System.out.println("栈是否为空:" + stack.empty());
        System.out.println("元素位置:" + stack.search("第一层")); // 从1开始计数
        
        // 注意:Vector 和 Stack 是遗留类,建议使用 ArrayList 和 Deque 替代
        // 推荐使用 Collections.synchronizedList() 或 Concurrent 包
        
        // 使用 Deque 替代 Stack
        Deque<String> dequeStack = new ArrayDeque<>();
        dequeStack.push("A");
        dequeStack.push("B");
        System.out.println("Deque 作为栈:" + dequeStack.pop()); // B
    }
}

3.4 Set 接口详解

3.4.1 HashSet:最常用的 Set 实现

特点

  • 底层是 HashMap
  • 元素无序(不保证顺序)
  • 不允许重复(通过 equals() 和 hashCode() 判断)
  • 允许 null 值
  • 时间复杂度 O(1)
public class HashSetDemo {
    public static void main(String[] args) {
        // 创建 HashSet
        Set<String> set = new HashSet<>();
        
        // 添加元素(重复元素不会被添加)
        set.add("Java");
        set.add("Python");
        set.add("Go");
        set.add("Java");  // 重复,不会添加
        System.out.println("集合内容:" + set); // 顺序不确定,如 [Java, Go, Python]
        
        // 批量添加
        set.addAll(Arrays.asList("C++", "JavaScript"));
        System.out.println("批量添加后:" + set);
        
        // 删除元素
        set.remove("Go");
        System.out.println("删除 Go 后:" + set);
        
        // 判断包含
        System.out.println("是否包含 Java?" + set.contains("Java"));
        
        // 大小
        System.out.println("集合大小:" + set.size());
        
        // 遍历
        set.forEach(System.out::println);
    }
}

3.4.2 自定义对象在 Set 中的去重

import java.util.*;

class Student {
    private String id;
    private String name;
    private int age;
    
    public Student(String id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    
    // 必须重写 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 age == student.age && 
               Objects.equals(id, student.id) && 
               Objects.equals(name, student.name);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(id, name, age);
    }
    
    @Override
    public String toString() {
        return "Student{id='" + id + "', name='" + name + "', age=" + age + "}";
    }
}

public class CustomObjectSet {
    public static void main(String[] args) {
        Set<Student> students = new HashSet<>();
        
        // 添加学生对象
        students.add(new Student("001", "张三", 20));
        students.add(new Student("002", "李四", 22));
        students.add(new Student("001", "张三", 20)); // 重复(根据内容判断)
        
        System.out.println("学生数量:" + students.size()); // 2
        students.forEach(System.out::println);
    }
}

3.4.3 LinkedHashSet:保持插入顺序

public class LinkedHashSetDemo {
    public static void main(String[] args) {
        // LinkedHashSet 维护了一个双向链表来记录插入顺序
        Set<String> linkedSet = new LinkedHashSet<>();
        linkedSet.add("A");
        linkedSet.add("B");
        linkedSet.add("C");
        linkedSet.add("D");
        linkedSet.add("A"); // 重复,不影响顺序
        
        System.out.println("LinkedHashSet(保持插入顺序):");
        linkedSet.forEach(System.out::println);
        // 输出:A, B, C, D
        
        // 对比 HashSet(顺序不确定)
        Set<String> hashSet = new HashSet<>();
        hashSet.addAll(linkedSet);
        System.out.println("\nHashSet(顺序不确定):");
        hashSet.forEach(System.out::println);
    }
}

3.4.4 TreeSet:排序的 Set

特点

  • 底层是 TreeMap(红黑树)
  • 元素自动排序(自然顺序或自定义比较器)
  • 不允许重复
  • 时间复杂度 O(log n)
public class TreeSetDemo {
    public static void main(String[] args) {
        // 自然排序(必须实现 Comparable)
        Set<Integer> numbers = new TreeSet<>();
        numbers.add(5);
        numbers.add(1);
        numbers.add(8);
        numbers.add(3);
        numbers.add(8); // 重复,忽略
        System.out.println("自然排序:" + numbers); // [1, 3, 5, 8]
        
        // 自定义排序(降序)
        Set<Integer> descNumbers = new TreeSet<>((a, b) -> b - a);
        descNumbers.addAll(numbers);
        System.out.println("降序排序:" + descNumbers); // [8, 5, 3, 1]
        
        // 自定义对象排序(必须实现 Comparable)
        Set<Person> people = new TreeSet<>();
        people.add(new Person("张三", 25));
        people.add(new Person("李四", 30));
        people.add(new Person("王五", 22));
        System.out.println("按年龄排序:");
        people.forEach(System.out::println);
        
        // TreeSet 的特殊方法
        TreeSet<Integer> treeSet = new TreeSet<>(numbers);
        System.out.println("第一个元素:" + treeSet.first());
        System.out.println("最后一个元素:" + treeSet.last());
        System.out.println("小于 5 的最大元素:" + treeSet.lower(5)); // 3
        System.out.println("小于等于 5 的最大元素:" + treeSet.floor(5)); // 5
        System.out.println("大于 3 的最小元素:" + treeSet.higher(3)); // 5
        System.out.println("大于等于 3 的最小元素:" + treeSet.ceiling(3)); // 3
        
        // 子集操作
        Set<Integer> subSet = treeSet.subSet(2, true, 6, true); // [2, 6]
        System.out.println("子集:" + subSet);
    }
}

// 必须实现 Comparable 接口
class Person implements Comparable<Person> {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public int compareTo(Person other) {
        return this.age - other.age; // 按年龄升序
        // return other.age - this.age; // 按年龄降序
    }
    
    @Override
    public String toString() {
        return name + "(" + age + ")";
    }
}

3.4.5 HashSet vs TreeSet vs LinkedHashSet 对比

特性 HashSet LinkedHashSet TreeSet
底层结构 哈希表(HashMap) 哈希表 + 双向链表 红黑树(TreeMap)
顺序 无序 插入顺序 排序顺序
时间复杂度 O(1) O(1) O(log n)
是否允许 null 允许(最多一个) 允许 不允许(需要比较)
使用场景 一般去重 需要保持插入顺序 需要排序

3.5 Map 接口详解

3.5.1 HashMap:最常用的 Map 实现

特点

  • 底层是哈希表(数组 + 链表/红黑树)
  • 键(Key)不可重复
  • 允许一个 null 键和多个 null 值
  • 线程不安全
  • 时间复杂度 O(1)
public class HashMapDemo {
    public static void main(String[] args) {
        // 创建 HashMap
        Map<String, Integer> map = new HashMap<>();
        
        // 1. 添加键值对
        map.put("Java", 95);
        map.put("Python", 88);
        map.put("Go", 92);
        map.put("Java", 98); // 覆盖之前的 value
        System.out.println("Map 内容:" + map); // {Java=98, Go=92, Python=88}
        
        // 2. putIfAbsent:如果键不存在才添加
        map.putIfAbsent("Rust", 85);
        map.putIfAbsent("Java", 100); // 键已存在,不会覆盖
        System.out.println("putIfAbsent 后:" + map);
        
        // 3. 获取值
        Integer javaScore = map.get("Java");
        System.out.println("Java 的分数:" + javaScore);
        System.out.println("不存在的键:" + map.get("PHP")); // null
        
        // 4. getOrDefault:获取值,不存在返回默认值
        System.out.println("PHP 的分数:" + map.getOrDefault("PHP", 0));
        
        // 5. 删除
        map.remove("Go");
        System.out.println("删除 Go 后:" + map);
        
        // 6. 判断键/值是否存在
        System.out.println("是否包含键 Java?" + map.containsKey("Java"));
        System.out.println("是否包含值 98?" + map.containsValue(98));
        
        // 7. 遍历
        System.out.println("\n=== 遍历 Entry ===");
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + " = " + entry.getValue());
        }
        
        System.out.println("\n=== 遍历键 ===");
        map.keySet().forEach(key -> System.out.println(key));
        
        System.out.println("\n=== 遍历值 ===");
        map.values().forEach(value -> System.out.println(value));
        
        // 8. 批量操作
        Map<String, Integer> other = new HashMap<>();
        other.put("C++", 90);
        other.put("JavaScript", 85);
        map.putAll(other);
        System.out.println("批量添加后:" + map);
        
        // 9. compute 操作
        map.compute("Java", (key, val) -> val + 5);
        System.out.println("Java 加分后:" + map.get("Java"));
        
        map.computeIfAbsent("C#", key -> 80);
        System.out.println("C# 添加后:" + map.get("C#"));
        
        map.computeIfPresent("Python", (key, val) -> val + 2);
        System.out.println("Python 加分后:" + map.get("Python"));
    }
}

3.5.2 深入理解 HashMap 的底层实现

public class HashMapDeepDive {
    public static void main(String[] args) {
        // 1. 初始容量和负载因子
        // 默认初始容量 16,负载因子 0.75
        Map<String, String> defaultMap = new HashMap<>();
        
        // 指定初始容量(推荐:容量 = 所需大小 / 0.75)
        Map<String, String> customMap = new HashMap<>(32);
        
        // 指定容量和负载因子
        Map<String, String> customMap2 = new HashMap<>(16, 0.8f);
        
        // 2. 扩容时机:size > capacity * loadFactor
        // 例如:16 * 0.75 = 12,当元素超过 12 个时扩容到 32
        
        // 3. 哈希冲突解决
        // - Java 8 之前:拉链法(链表)
        // - Java 8 及以后:链表长度 > 8 且数组长度 >= 64 时转为红黑树
        
        // 4. 键的要求
        // 键必须正确重写 hashCode() 和 equals()
        // 不可变对象作为键更安全(如 String, Integer)
        
        // 5. 性能优化
        Map<String, String> optimized = new HashMap<>();
        // 预分配容量,避免频繁扩容
        int expectedSize = 100;
        int capacity = (int) (expectedSize / 0.75) + 1;
        Map<String, String> preAllocated = new HashMap<>(capacity);
    }
}

3.5.3 LinkedHashMap:保持插入顺序的 Map

public class LinkedHashMapDemo {
    public static void main(String[] args) {
        // 默认按照插入顺序
        Map<String, String> linkedMap = new LinkedHashMap<>();
        linkedMap.put("A", "Apple");
        linkedMap.put("B", "Banana");
        linkedMap.put("C", "Cherry");
        linkedMap.put("D", "Durian");
        
        System.out.println("LinkedHashMap(插入顺序):");
        linkedMap.forEach((k, v) -> System.out.println(k + " -> " + v));
        
        // 访问顺序模式(LRU 缓存实现)
        // accessOrder = true 表示按访问顺序排序(最近访问的在最后)
        LinkedHashMap<String, String> lruMap = new LinkedHashMap<>(16, 0.75f, true);
        lruMap.put("A", "Apple");
        lruMap.put("B", "Banana");
        lruMap.put("C", "Cherry");
        
        System.out.println("\n初始顺序:");
        lruMap.forEach((k, v) -> System.out.println(k + " -> " + v));
        
        // 访问元素 A
        lruMap.get("A");
        System.out.println("\n访问 A 后(A 移到最后):");
        lruMap.forEach((k, v) -> System.out.println(k + " -> " + v));
        
        // 访问元素 C
        lruMap.get("C");
        System.out.println("\n访问 C 后(C 移到最后):");
        lruMap.forEach((k, v) -> System.out.println(k + " -> " + v));
        
        // 实现 LRU 缓存
        LRUCache<String, String> cache = new LRUCache<>(3);
        cache.put("1", "One");
        cache.put("2", "Two");
        cache.put("3", "Three");
        System.out.println("\n缓存满后:");
        cache.forEach((k, v) -> System.out.println(k + " -> " + v));
        
        cache.get("1");
        cache.put("4", "Four"); // 会移除最久未使用的 "2"
        System.out.println("\n添加 4 后(2 被移除):");
        cache.forEach((k, v) -> System.out.println(k + " -> " + v));
    }
}

// 自定义 LRU 缓存
class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int maxSize;
    
    public LRUCache(int maxSize) {
        super(16, 0.75f, true);
        this.maxSize = maxSize;
    }
    
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > maxSize;
    }
}

3.5.4 TreeMap:排序的 Map

public class TreeMapDemo {
    public static void main(String[] args) {
        // 自然排序(键必须实现 Comparable)
        TreeMap<String, Integer> treeMap = new TreeMap<>();
        treeMap.put("Banana", 2);
        treeMap.put("Apple", 5);
        treeMap.put("Cherry", 3);
        treeMap.put("Date", 1);
        System.out.println("TreeMap(按键排序):");
        treeMap.forEach((k, v) -> System.out.println(k + " -> " + v));
        
        // 自定义排序(降序)
        TreeMap<String, Integer> descMap = new TreeMap<>((a, b) -> b.compareTo(a));
        descMap.putAll(treeMap);
        System.out.println("\n降序排列:");
        descMap.forEach((k, v) -> System.out.println(k + " -> " + v));
        
        // TreeMap 的特殊方法
        TreeMap<Integer, String> map = new TreeMap<>();
        map.put(10, "Ten");
        map.put(20, "Twenty");
        map.put(30, "Thirty");
        map.put(40, "Forty");
        map.put(50, "Fifty");
        
        System.out.println("\n=== 导航方法 ===");
        System.out.println("最小键:" + map.firstKey() + " -> " + map.firstEntry());
        System.out.println("最大键:" + map.lastKey() + " -> " + map.lastEntry());
        System.out.println("小于 30 的最大键:" + map.lowerKey(30) + " -> " + map.lowerEntry(30));
        System.out.println("小于等于 30 的最大键:" + map.floorKey(30) + " -> " + map.floorEntry(30));
        System.out.println("大于 30 的最小键:" + map.higherKey(30) + " -> " + map.higherEntry(30));
        System.out.println("大于等于 30 的最小键:" + map.ceilingKey(30) + " -> " + map.ceilingEntry(30));
        
        // 子 Map
        System.out.println("\n键在 20-40 之间的子 Map:");
        map.subMap(20, true, 40, true).forEach((k, v) -> System.out.println(k + " -> " + v));
    }
}

3.5.5 Hashtable 与 ConcurrentHashMap

import java.util.concurrent.*;

public class MapThreadSafety {
    public static void main(String[] args) {
        // 1. Hashtable:线程安全(全表锁),遗留类,不推荐
        Hashtable<String, String> hashtable = new Hashtable<>();
        hashtable.put("key", "value");
        // hashtable.put(null, "value"); // 不允许 null 键
        // hashtable.put("key", null);    // 不允许 null 值
        
        // 2. Collections.synchronizedMap:包装成线程安全的 Map
        Map<String, String> synchronizedMap = Collections.synchronizedMap(new HashMap<>());
        // 使用时需要外部同步
        synchronized (synchronizedMap) {
            synchronizedMap.put("key", "value");
        }
        
        // 3. ConcurrentHashMap:推荐使用(分段锁,高性能)
        ConcurrentHashMap<String, String> concurrentMap = new ConcurrentHashMap<>();
        concurrentMap.put("key", "value");
        concurrentMap.putIfAbsent("key2", "value2");
        
        // ConcurrentHashMap 的原子操作
        concurrentMap.compute("key", (k, v) -> v + " updated");
        String result = concurrentMap.get("key");
        System.out.println("ConcurrentHashMap 结果:" + result);
        
        // 4. 性能对比(单线程场景 HashMap 更快)
        // 多线程并发场景 ConcurrentHashMap 优于 Hashtable
        
        // 5. 使用 ConcurrentHashMap 的推荐方式
        ConcurrentHashMap<String, Integer> scores = new ConcurrentHashMap<>();
        
        // 原子更新
        scores.compute("player1", (key, val) -> val == null ? 1 : val + 1);
        System.out.println("player1 分数:" + scores.get("player1"));
        
        // 或者使用累加操作
        scores.merge("player2", 1, Integer::sum);
        scores.merge("player2", 1, Integer::sum);
        System.out.println("player2 分数:" + scores.get("player2")); // 2
    }
}

3.6 Queue 和 Deque 接口

3.6.1 Queue 接口:队列(FIFO)

public class QueueDemo {
    public static void main(String[] args) {
        // 1. LinkedList 作为队列
        Queue<String> queue = new LinkedList<>();
        
        // 入队操作
        queue.offer("任务1");
        queue.offer("任务2");
        queue.offer("任务3");
        
        System.out.println("队列:" + queue); // [任务1, 任务2, 任务3]
        
        // 查看队首(不移除)
        System.out.println("队首元素:" + queue.peek()); // 任务1
        
        // 出队操作
        System.out.println("处理:" + queue.poll()); // 任务1
        System.out.println("处理:" + queue.poll()); // 任务2
        System.out.println("处理后的队列:" + queue); // [任务3]
        
        // 2. PriorityQueue:优先级队列(自然顺序或自定义比较器)
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
        priorityQueue.offer(10);
        priorityQueue.offer(5);
        priorityQueue.offer(8);
        priorityQueue.offer(1);
        
        System.out.println("\n优先级队列(最小堆):");
        while (!priorityQueue.isEmpty()) {
            System.out.println("出队:" + priorityQueue.poll());
        }
        // 输出:1, 5, 8, 10
        
        // 最大堆(自定义比较器)
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
        maxHeap.offer(10);
        maxHeap.offer(5);
        maxHeap.offer(8);
        maxHeap.offer(1);
        
        System.out.println("\n最大堆:");
        while (!maxHeap.isEmpty()) {
            System.out.println("出队:" + maxHeap.poll());
        }
        // 输出:10, 8, 5, 1
    }
}

3.6.2 Deque 接口:双端队列

public class DequeDemo {
    public static void main(String[] args) {
        // ArrayDeque:推荐的高性能双端队列(优于 LinkedList)
        Deque<String> deque = new ArrayDeque<>();
        
        // 作为队列使用(FIFO)
        deque.offer("A");
        deque.offer("B");
        deque.offer("C");
        System.out.println("队列操作:" + deque.poll()); // A
        
        // 作为栈使用(LIFO)
        Deque<String> stack = new ArrayDeque<>();
        stack.push("第一层");
        stack.push("第二层");
        stack.push("第三层");
        System.out.println("\n栈操作:");
        System.out.println("栈顶:" + stack.peek()); // 第三层
        System.out.println("弹出:" + stack.pop());  // 第三层
        System.out.println("弹出后栈顶:" + stack.peek()); // 第二层
        
        // 双端操作
        ArrayDeque<String> both = new ArrayDeque<>();
        both.addFirst("左");
        both.addLast("右");
        both.addFirst("左左");
        both.addLast("右右");
        System.out.println("\n双端队列:" + both);
        System.out.println("移除左侧:" + both.removeFirst()); // 左左
        System.out.println("移除右侧:" + both.removeLast());  // 右右
        System.out.println("剩余:" + both); // [左, 右]
    }
}

3.7 集合工具类:Collections

import java.util.*;

public class CollectionsUtility {
    public static void main(String[] args) {
        // 1. 创建不可变集合
        List<String> unmodifiableList = Collections.emptyList(); // 空列表
        Set<String> singleton = Collections.singleton("单元素"); // 单元素集合
        Map<String, String> emptyMap = Collections.emptyMap();
        
        // 2. 线程安全的包装
        List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());
        Map<String, String> synchronizedMap = Collections.synchronizedMap(new HashMap<>());
        
        // 3. 排序
        List<Integer> numbers = new ArrayList<>(Arrays.asList(5, 2, 8, 1, 9, 3));
        Collections.sort(numbers); // 升序
        System.out.println("升序:" + numbers);
        Collections.sort(numbers, (a, b) -> b - a); // 降序
        System.out.println("降序:" + numbers);
        
        // 4. 二分查找
        Collections.sort(numbers); // 必须先排序
        int index = Collections.binarySearch(numbers, 5);
        System.out.println("5 的位置:" + index);
        
        // 5. 反转
        Collections.reverse(numbers);
        System.out.println("反转:" + numbers);
        
        // 6. 打乱(随机排列)
        Collections.shuffle(numbers);
        System.out.println("打乱:" + numbers);
        
        // 7. 最大值/最小值
        System.out.println("最大值:" + Collections.max(numbers));
        System.out.println("最小值:" + Collections.min(numbers));
        
        // 8. 频率统计
        List<String> words = Arrays.asList("a", "b", "a", "c", "a", "b");
        System.out.println("'a' 出现次数:" + Collections.frequency(words, "a"));
        
        // 9. 复制
        List<Integer> dest = new ArrayList<>(Collections.nCopies(numbers.size(), 0));
        Collections.copy(dest, numbers);
        System.out.println("复制后的列表:" + dest);
        
        // 10. 填充
        Collections.fill(dest, 999);
        System.out.println("填充后的列表:" + dest);
        
        // 11. 旋转
        Collections.rotate(numbers, 2);
        System.out.println("旋转 2 位:" + numbers);
        
        // 12. 交换
        Collections.swap(numbers, 0, numbers.size() - 1);
        System.out.println("交换首尾:" + numbers);
    }
}

3.8 集合转换与数组互转

public class CollectionConversion {
    public static void main(String[] args) {
        // 1. 数组 → List
        String[] array = {"A", "B", "C"};
        List<String> list = Arrays.asList(array);
        // 注意:Arrays.asList 返回的是固定大小的 List,不能修改大小
        System.out.println("数组转 List:" + list);
        // list.add("D"); // 抛出 UnsupportedOperationException
        
        // 可修改的 List
        List<String> mutableList = new ArrayList<>(Arrays.asList(array));
        mutableList.add("D");
        System.out.println("可修改的 List:" + mutableList);
        
        // 2. List → 数组
        List<Integer> numList = Arrays.asList(1, 2, 3);
        Integer[] numArray = numList.toArray(new Integer[0]);
        System.out.println("List 转数组:" + Arrays.toString(numArray));
        
        // 3. 数组 → Set
        Set<String> set = new HashSet<>(Arrays.asList(array));
        System.out.println("数组转 Set:" + set);
        
        // 4. Set → 数组
        String[] setArray = set.toArray(new String[0]);
        
        // 5. List → Set(去重)
        List<String> duplicateList = Arrays.asList("A", "B", "A", "C", "B");
        Set<String> uniqueSet = new HashSet<>(duplicateList);
        System.out.println("List 去重:" + uniqueSet);
        
        // 6. Set → List
        List<String> listFromSet = new ArrayList<>(uniqueSet);
        
        // 7. Map 的转换
        Map<String, Integer> map = new HashMap<>();
        map.put("A", 1);
        map.put("B", 2);
        map.put("C", 3);
        
        // Map 的键集
        Set<String> keySet = map.keySet();
        // Map 的值集合
        Collection<Integer> values = map.values();
        // Map 的 Entry 集合
        Set<Map.Entry<String, Integer>> entries = map.entrySet();
        
        // Java 9+ 创建不可变集合
        List<String> immutableList = List.of("A", "B", "C");
        Set<String> immutableSet = Set.of("A", "B", "C");
        Map<String, Integer> immutableMap = Map.of("A", 1, "B", 2, "C", 3);
        
        // Java 10+ 使用 var(类型推断)
        var varList = new ArrayList<String>();
        varList.add("Hello");
        varList.add("World");
        System.out.println("var 类型推断:" + varList);
    }
}

3.9 Stream API:集合的函数式编程

import java.util.*;
import java.util.stream.*;

public class StreamAPIDemo {
    public static void main(String[] args) {
        List<Employee> employees = Arrays.asList(
            new Employee("张三", 30, 8000, "研发部"),
            new Employee("李四", 25, 6000, "市场部"),
            new Employee("王五", 35, 10000, "研发部"),
            new Employee("赵六", 28, 7500, "财务部"),
            new Employee("钱七", 32, 9000, "研发部")
        );
        
        // 1. 过滤
        List<Employee> highSalary = employees.stream()
                .filter(e -> e.getSalary() > 8000)
                .collect(Collectors.toList());
        System.out.println("薪资 > 8000 的员工:" + highSalary);
        
        // 2. 映射(提取属性)
        List<String> names = employees.stream()
                .map(Employee::getName)
                .collect(Collectors.toList());
        System.out.println("所有员工姓名:" + names);
        
        // 3. 排序
        List<Employee> sorted = employees.stream()
                .sorted((a, b) -> b.getSalary() - a.getSalary())
                .collect(Collectors.toList());
        System.out.println("按薪资降序排序:");
        sorted.forEach(System.out::println);
        
        // 4. 聚合操作
        double avgSalary = employees.stream()
                .mapToDouble(Employee::getSalary)
                .average()
                .orElse(0);
        System.out.println("平均薪资:" + avgSalary);
        
        double totalSalary = employees.stream()
                .mapToDouble(Employee::getSalary)
                .sum();
        System.out.println("薪资总和:" + totalSalary);
        
        // 5. 分组
        Map<String, List<Employee>> byDepartment = employees.stream()
                .collect(Collectors.groupingBy(Employee::getDepartment));
        System.out.println("按部门分组:");
        byDepartment.forEach((dept, empList) -> {
            System.out.println(dept + ": " + empList.size() + "人");
        });
        
        // 6. 统计
        IntSummaryStatistics stats = employees.stream()
                .mapToInt(Employee::getSalary)
                .summaryStatistics();
        System.out.println("统计信息:");
        System.out.println("  最大值:" + stats.getMax());
        System.out.println("  最小值:" + stats.getMin());
        System.out.println("  平均值:" + stats.getAverage());
        System.out.println("  总和:" + stats.getSum());
        System.out.println("  计数:" + stats.getCount());
        
        // 7. 查找
        Optional<Employee> maxSalary = employees.stream()
                .max(Comparator.comparing(Employee::getSalary));
        maxSalary.ifPresent(e -> System.out.println("最高薪员工:" + e));
        
        // 8. 匹配
        boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 10000);
        System.out.println("是否有薪资超过 10000 的员工:" + anyMatch);
        
        boolean allMatch = employees.stream().allMatch(e -> e.getSalary() > 5000);
        System.out.println("是否所有员工薪资都超过 5000:" + allMatch);
        
        // 9. 归约
        double total = employees.stream()
                .map(Employee::getSalary)
                .reduce(0.0, Double::sum);
        System.out.println("使用 reduce 求和:" + total);
        
        // 10. 链式操作
        String result = employees.stream()
                .filter(e -> e.getDepartment().equals("研发部"))
                .map(Employee::getName)
                .sorted()
                .collect(Collectors.joining(", "));
        System.out.println("研发部员工(按姓名排序):" + result);
        
        // 11. 并行流(多线程处理)
        long startTime = System.currentTimeMillis();
        long count = employees.parallelStream()
                .filter(e -> e.getAge() > 30)
                .count();
        long endTime = System.currentTimeMillis();
        System.out.println("年龄 > 30 的员工数:" + count);
        System.out.println("并行流耗时:" + (endTime - startTime) + "ms");
    }
}

class Employee {
    private String name;
    private int age;
    private double salary;
    private String department;
    
    public Employee(String name, int age, double salary, String department) {
        this.name = name;
        this.age = age;
        this.salary = salary;
        this.department = department;
    }
    
    // Getters
    public String getName() { return name; }
    public int getAge() { return age; }
    public double getSalary() { return salary; }
    public String getDepartment() { return department; }
    
    @Override
    public String toString() {
        return String.format("%s(年龄:%d, 薪资:%.0f, 部门:%s)", 
                name, age, salary, department);
    }
}

3.10 集合使用最佳实践

public class CollectionBestPractices {
    public static void main(String[] args) {
        // 1. 选择正确的集合类型
        // - 需要快速查找:HashMap
        // - 需要保持顺序:LinkedHashMap / LinkedHashSet
        // - 需要排序:TreeMap / TreeSet
        // - 需要快速随机访问:ArrayList
        // - 需要频繁插入删除:LinkedList
        // - 需要线程安全:ConcurrentHashMap / CopyOnWriteArrayList
        
        // 2. 初始化时指定容量
        int expectedSize = 1000;
        Map<String, String> map = new HashMap<>((int) (expectedSize / 0.75) + 1);
        
        // 3. 使用泛型确保类型安全
        List<String> list = new ArrayList<>(); // 不允许添加非 String 类型
        
        // 4. 使用不可变集合
        List<String> immutable = List.of("A", "B", "C");
        
        // 5. 避免在遍历时修改集合
        List<String> items = new ArrayList<>(Arrays.asList("A", "B", "C"));
        // 错误方式
        // for (String item : items) {
        //     if ("B".equals(item)) items.remove(item); // ConcurrentModificationException
        // }
        
        // 正确方式1:使用 Iterator
        Iterator<String> iterator = items.iterator();
        while (iterator.hasNext()) {
            if ("B".equals(iterator.next())) {
                iterator.remove();
            }
        }
        System.out.println("移除 B 后:" + items);
        
        // 正确方式2:使用 removeIf
        items.removeIf(item -> "C".equals(item));
        System.out.println("移除 C 后:" + items);
        
        // 6. 使用 isEmpty() 而不是 size() == 0
        if (items.isEmpty()) {
            System.out.println("集合为空");
        }
        
        // 7. 返回空集合而不是 null
        List<String> getList() {
            // 不要返回 null,而是返回空集合
            return Collections.emptyList();
            // 或者 return new ArrayList<>();
        }
        
        // 8. 使用 Optional 处理可能为 null 的值
        Map<String, String> data = new HashMap<>();
        Optional.ofNullable(data.get("key"))
                .ifPresent(value -> System.out.println("值:" + value));
    }
}

第四章:集合框架总结

4.1 选择指南速查表

需求场景 推荐集合
存储有序、可重复的数据 ArrayList
频繁在中间位置插入/删除 LinkedList
去重(无序) HashSet
去重(保持插入顺序) LinkedHashSet
去重(自动排序) TreeSet
键值对存储 HashMap
键值对(保持插入顺序) LinkedHashMap
键值对(自动排序) TreeMap
线程安全的键值对 ConcurrentHashMap
队列(FIFO) ArrayDeque
栈(LIFO) ArrayDeque
优先级队列 PriorityQueue

4.2 时间复杂度总结

集合 添加 删除 查找 访问
ArrayList O(1)* O(n) O(n) O(1)
LinkedList O(1) O(1) O(n) O(n)
HashSet O(1) O(1) O(1) -
LinkedHashSet O(1) O(1) O(1) -
TreeSet O(log n) O(log n) O(log n) -
HashMap O(1) O(1) O(1) -
LinkedHashMap O(1) O(1) O(1) -
TreeMap O(log n) O(log n) O(log n) -

*注:尾部添加为 O(1),扩容时可能为 O(n)


结语

本章详细介绍了 Java 集合框架的方方面面,从 List、Set、Map 三大体系,到 Queue、Deque 等队列结构,再到 Stream API 的函数式编程。集合框架是 Java 开发中最常用的工具,熟练掌握它们将让你的代码更加简洁、高效。

建议练习:

  1. 实现一个简单的学生管理系统(使用 HashMap 存储学生信息)
  2. 使用 TreeSet 实现一个自动排序的任务列表
  3. 使用 Stream API 处理大量数据(过滤、分组、统计)
  4. 实现一个 LRU 缓存(基于 LinkedHashMap)

下一章预告:我们将进入 Java IO 与文件操作,学习如何读写文件、处理流、以及 NIO 的新特性。敬请期待!📁

评论