Java集合框架完全指南

Java集合框架完全指南

学习目标

  • 深入理解Java集合框架的整体架构和设计思想
  • 掌握List、Set、Map三大集合体系的核心实现类
  • 能够根据业务场景选择合适的集合类型
  • 理解集合底层数据结构和性能特点
  • 熟练使用集合的常用操作和遍历方式
  • 掌握线程安全集合的使用场景

一、概念引入

1.1 为什么需要集合框架?

在实际开发中,我们经常需要存储和操作一组对象:

场景1:用户列表管理

java 复制代码
// ✗ 使用数组的问题
User[] users = new User[10];  // 长度固定,无法动态扩容
users[0] = new User("张三");
users[1] = new User("李四");
// 如果用户超过10个怎么办?需要手动扩容,非常麻烦

// ✓ 使用集合
List<User> users = new ArrayList<>();
users.add(new User("张三"));
users.add(new User("李四"));
// 自动扩容,使用方便

场景2:商品去重

java 复制代码
// ✗ 使用数组需要手动去重
String[] products = {"手机", "电脑", "手机", "平板"};
// 需要写循环判断重复,代码复杂

// ✓ 使用Set自动去重
Set<String> products = new HashSet<>();
products.add("手机");
products.add("电脑");
products.add("手机");  // 自动忽略重复
products.add("平板");
// 结果:[手机, 电脑, 平板]

场景3:配置信息存储

java 复制代码
// ✗ 使用两个数组维护键值对
String[] keys = {"host", "port", "username"};
String[] values = {"localhost", "3306", "root"};
// 需要通过索引关联,容易出错

// ✓ 使用Map存储键值对
Map<String, String> config = new HashMap<>();
config.put("host", "localhost");
config.put("port", "3306");
config.put("username", "root");
// 通过key直接获取value,清晰明了

数组的局限性:

  • 长度固定,无法动态扩容
  • 只能存储同一类型的数据
  • 没有提供便捷的增删改查方法
  • 无法自动去重
  • 不支持键值对存储

集合框架的优势:

  • 动态扩容,自动管理内存
  • 提供丰富的操作方法
  • 支持泛型,类型安全
  • 多种数据结构实现,满足不同需求
  • 线程安全和非线程安全版本可选

1.2 集合框架的核心思想

复制代码
Java集合框架 = 接口 + 实现类 + 算法

设计理念:

  • 接口定义规范:Collection、List、Set、Map等接口定义了集合的标准操作
  • 实现类提供功能:ArrayList、LinkedList、HashSet、HashMap等提供具体实现
  • 算法独立封装:Collections工具类提供排序、查找等算法

集合框架的层次结构:

swift 复制代码
                    Iterable<E>
                        ↑
                   Collection<E>
                        ↑
        ┌───────────────┼───────────────┐
        ↑               ↑               ↑
     List<E>         Set<E>         Queue<E>
        ↑               ↑               ↑
   ┌────┴────┐     ┌────┴────┐     ┌────┴────┐
   ↑         ↑     ↑         ↑     ↑         ↑
ArrayList LinkedList HashSet TreeSet PriorityQueue ArrayDeque


                    Map<K,V>
                        ↑
        ┌───────────────┼───────────────┐
        ↑               ↑               ↑
    HashMap         TreeMap        LinkedHashMap
        ↑
   Hashtable
        ↑
   Properties

1.3 集合框架的三大体系

体系 接口 特点 常用实现类
List 有序、可重复 按索引访问 ArrayList、LinkedList、Vector
Set 无序、不可重复 元素唯一 HashSet、TreeSet、LinkedHashSet
Map 键值对 key唯一 HashMap、TreeMap、LinkedHashMap

二、List集合详解

2.1 List接口概述

List的核心特点:

  • 有序:元素按照插入顺序排列
  • 可重复:允许存储重复元素
  • 索引访问:可以通过索引(下标)访问元素

List接口的常用方法:

java 复制代码
public interface List<E> extends Collection<E> {
    // 添加元素
    boolean add(E e);                    // 在末尾添加
    void add(int index, E element);      // 在指定位置插入
    
    // 删除元素
    E remove(int index);                 // 删除指定位置的元素
    boolean remove(Object o);            // 删除指定元素
    
    // 修改元素
    E set(int index, E element);         // 替换指定位置的元素
    
    // 查询元素
    E get(int index);                    // 获取指定位置的元素
    int indexOf(Object o);               // 查找元素第一次出现的位置
    int lastIndexOf(Object o);           // 查找元素最后一次出现的位置
    
    // 其他操作
    int size();                          // 获取元素个数
    boolean isEmpty();                   // 判断是否为空
    boolean contains(Object o);          // 判断是否包含某元素
    void clear();                        // 清空集合
    
    // 子列表
    List<E> subList(int fromIndex, int toIndex);  // 获取子列表
}

2.2 ArrayList详解

ArrayList的核心特点:

  • 底层使用动态数组实现
  • 查询快:O(1)时间复杂度
  • 增删慢:需要移动元素,O(n)时间复杂度
  • 非线程安全

ArrayList的底层原理:

ini 复制代码
ArrayList内部结构:
┌─────────────────────────────────────────┐
│ ArrayList                                │
├─────────────────────────────────────────┤
│ elementData: Object[]  ← 存储元素的数组  │
│ size: int              ← 实际元素个数    │
│ DEFAULT_CAPACITY = 10  ← 默认容量       │
└─────────────────────────────────────────┘

初始化:
elementData = {}  (空数组)
size = 0

第一次add:
elementData = new Object[10]  (扩容到10)
size = 1

扩容机制:
当 size == elementData.length 时
newCapacity = oldCapacity + (oldCapacity >> 1)  // 扩容1.5倍
elementData = Arrays.copyOf(elementData, newCapacity)

ArrayList的基本使用:

java 复制代码
// 1. 创建ArrayList
List<String> list = new ArrayList<>();           // 默认容量10
List<String> list2 = new ArrayList<>(20);        // 指定初始容量
List<String> list3 = new ArrayList<>(list);      // 从其他集合创建

// 2. 添加元素
list.add("Java");                    // 在末尾添加
list.add("Python");
list.add("C++");
list.add(1, "Go");                   // 在索引1处插入
// 结果:[Java, Go, Python, C++]

// 3. 获取元素
String first = list.get(0);          // 获取第一个元素:Java
String last = list.get(list.size() - 1);  // 获取最后一个元素:C++

// 4. 修改元素
list.set(1, "JavaScript");           // 将索引1的元素改为JavaScript
// 结果:[Java, JavaScript, Python, C++]

// 5. 删除元素
list.remove(0);                      // 删除索引0的元素
list.remove("Python");               // 删除指定元素
// 结果:[JavaScript, C++]

// 6. 查找元素
int index = list.indexOf("C++");     // 查找元素位置:1
boolean exists = list.contains("Java");  // 判断是否存在:false

// 7. 遍历元素
// 方式1:for循环
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

// 方式2:增强for循环
for (String item : list) {
    System.out.println(item);
}

// 方式3:Iterator迭代器
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

// 方式4:forEach + Lambda
list.forEach(item -> System.out.println(item));

// 方式5:Stream API
list.stream().forEach(System.out::println);

ArrayList的性能分析:

操作 时间复杂度 说明
get(index) O(1) 直接通过数组下标访问
add(e) O(1) 在末尾添加,均摊O(1)
add(index, e) O(n) 需要移动后续元素
remove(index) O(n) 需要移动后续元素
contains(o) O(n) 需要遍历查找
size() O(1) 直接返回size字段

ArrayList的使用场景:

java 复制代码
// ✓ 适合场景1:频繁查询
List<Product> products = new ArrayList<>();
// 根据索引快速获取商品信息
Product p = products.get(5);

// ✓ 适合场景2:在末尾添加元素
List<String> logs = new ArrayList<>();
logs.add("日志1");  // 在末尾添加,性能好
logs.add("日志2");

// ✗ 不适合场景1:频繁在中间插入删除
List<Integer> numbers = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
    numbers.add(0, i);  // 每次都在开头插入,性能差
}

// ✗ 不适合场景2:大量数据的频繁增删
List<String> queue = new ArrayList<>();
queue.add(0, "新元素");  // 在开头插入
queue.remove(0);         // 删除第一个元素
// 这种场景应该使用LinkedList

2.3 LinkedList详解

LinkedList的核心特点:

  • 底层使用双向链表实现
  • 增删快:O(1)时间复杂度(在已知节点位置的情况下)
  • 查询慢:O(n)时间复杂度,需要遍历
  • 非线程安全
  • 实现了Deque接口,可以作为队列、栈使用

LinkedList的底层原理:

yaml 复制代码
LinkedList内部结构:
┌─────────────────────────────────────────┐
│ LinkedList                               │
├─────────────────────────────────────────┤
│ first: Node<E>   ← 指向第一个节点       │
│ last: Node<E>    ← 指向最后一个节点     │
│ size: int        ← 元素个数             │
└─────────────────────────────────────────┘

Node节点结构:
┌─────────────────────────────────────────┐
│ Node<E>                                  │
├─────────────────────────────────────────┤
│ item: E          ← 存储的元素           │
│ next: Node<E>    ← 指向下一个节点       │
│ prev: Node<E>    ← 指向上一个节点       │
└─────────────────────────────────────────┘

双向链表示意图:
null ← [Node1] ↔ [Node2] ↔ [Node3] → null
        ↑                      ↑
      first                  last

LinkedList的基本使用:

java 复制代码
// 1. 创建LinkedList
List<String> list = new LinkedList<>();

// 2. 添加元素
list.add("A");                    // 在末尾添加
list.add("B");
list.add("C");
list.add(1, "X");                 // 在索引1处插入
// 结果:[A, X, B, C]

// 3. 作为双端队列使用
LinkedList<String> deque = new LinkedList<>();

// 在头部操作
deque.addFirst("头部元素");       // 在开头添加
deque.offerFirst("头部元素2");    // 在开头添加(推荐)
String first = deque.getFirst(); // 获取第一个元素
String poll = deque.pollFirst(); // 获取并删除第一个元素

// 在尾部操作
deque.addLast("尾部元素");        // 在末尾添加
deque.offerLast("尾部元素2");     // 在末尾添加(推荐)
String last = deque.getLast();   // 获取最后一个元素
String pollLast = deque.pollLast(); // 获取并删除最后一个元素

// 4. 作为栈使用
LinkedList<String> stack = new LinkedList<>();
stack.push("元素1");              // 入栈
stack.push("元素2");
stack.push("元素3");
String top = stack.pop();         // 出栈:元素3
String peek = stack.peek();       // 查看栈顶:元素2

// 5. 作为队列使用
LinkedList<String> queue = new LinkedList<>();
queue.offer("任务1");             // 入队
queue.offer("任务2");
queue.offer("任务3");
String task = queue.poll();       // 出队:任务1
String head = queue.peek();       // 查看队首:任务2

LinkedList vs ArrayList对比:

维度 ArrayList LinkedList
底层结构 动态数组 双向链表
随机访问 O(1) 快 O(n) 慢
插入删除(末尾) O(1) 快 O(1) 快
插入删除(中间) O(n) 慢 O(1) 快(已知节点)
内存占用 连续内存 不连续,额外存储指针
适用场景 查询多 增删多

LinkedList的使用场景:

java 复制代码
// ✓ 适合场景1:频繁在头尾插入删除
LinkedList<String> queue = new LinkedList<>();
queue.addFirst("新任务");  // 在开头插入,O(1)
queue.removeLast();        // 删除最后一个,O(1)

// ✓ 适合场景2:实现队列或栈
// 队列:先进先出
LinkedList<Order> orderQueue = new LinkedList<>();
orderQueue.offer(new Order());  // 入队
Order order = orderQueue.poll(); // 出队

// 栈:后进先出
LinkedList<String> stack = new LinkedList<>();
stack.push("A");  // 入栈
String top = stack.pop();  // 出栈

// ✗ 不适合场景:频繁随机访问
LinkedList<Integer> numbers = new LinkedList<>();
for (int i = 0; i < 10000; i++) {
    numbers.add(i);
}
// 随机访问性能差
int value = numbers.get(5000);  // 需要遍历5000个节点

2.4 Vector详解

Vector的核心特点:

  • 底层使用动态数组实现(与ArrayList相同)
  • 线程安全:所有方法都加了synchronized
  • 扩容机制:默认扩容2倍(ArrayList是1.5倍)
  • 性能较差:因为同步开销
  • 已过时,不推荐使用

Vector vs ArrayList:

java 复制代码
// Vector:线程安全但性能差
Vector<String> vector = new Vector<>();
vector.add("A");  // synchronized方法,线程安全

// ArrayList:非线程安全但性能好
ArrayList<String> list = new ArrayList<>();
list.add("A");    // 非synchronized,性能好

// 如果需要线程安全的List,推荐使用:
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
// 或者使用并发包中的CopyOnWriteArrayList
List<String> cowList = new CopyOnWriteArrayList<>();

2.5 List的选择决策树

markdown 复制代码
需要List集合
    ↓
是否需要线程安全?
    ├─ 是 → CopyOnWriteArrayList(读多写少)
    │       或 Collections.synchronizedList()
    │
    └─ 否 → 是否频繁随机访问?
            ├─ 是 → ArrayList(查询多)
            │
            └─ 否 → 是否频繁在头尾插入删除?
                    ├─ 是 → LinkedList(增删多)
                    │
                    └─ 否 → ArrayList(通用场景)

三、Set集合详解

3.1 Set接口概述

Set的核心特点:

  • 无序:元素没有固定顺序(HashSet)或按某种规则排序(TreeSet)
  • 不可重复:不允许存储重复元素
  • 无索引:不能通过索引访问元素

Set接口的常用方法:

java 复制代码
public interface Set<E> extends Collection<E> {
    // 添加元素
    boolean add(E e);              // 添加元素,重复则返回false
    
    // 删除元素
    boolean remove(Object o);      // 删除指定元素
    
    // 查询元素
    boolean contains(Object o);    // 判断是否包含某元素
    int size();                    // 获取元素个数
    boolean isEmpty();             // 判断是否为空
    
    // 集合操作
    void clear();                  // 清空集合
    
    // 注意:Set没有get(index)方法,因为没有索引
}

Set的去重原理:

scss 复制代码
Set如何判断元素重复?
    ↓
1. 先调用 hashCode() 方法获取哈希值
    ↓
2. 比较哈希值是否相同
    ├─ 不同 → 不重复,直接添加
    │
    └─ 相同 → 调用 equals() 方法比较内容
            ├─ 不同 → 不重复,添加(哈希冲突)
            │
            └─ 相同 → 重复,不添加

3.2 HashSet详解

HashSet的核心特点:

  • 底层使用HashMap实现(元素作为HashMap的key)
  • 无序:不保证元素顺序
  • 不可重复:通过hashCode和equals判断
  • 允许null值
  • 非线程安全
  • 查询快:O(1)时间复杂度

HashSet的底层原理:

vbnet 复制代码
HashSet内部结构:
┌─────────────────────────────────────────┐
│ HashSet<E>                               │
├─────────────────────────────────────────┤
│ map: HashMap<E, Object>                  │
│   ↑                                      │
│   └─ 元素存储在HashMap的key中            │
│      value统一使用PRESENT对象            │
└─────────────────────────────────────────┘

添加元素的过程:
set.add(element)
    ↓
map.put(element, PRESENT)
    ↓
计算element的hashCode
    ↓
根据hashCode确定存储位置
    ↓
如果位置为空,直接存储
如果位置不为空,调用equals比较
    ├─ equals返回true → 重复,不添加
    └─ equals返回false → 不重复,添加(链表或红黑树)

HashSet的基本使用:

java 复制代码
// 1. 创建HashSet
Set<String> set = new HashSet<>();

// 2. 添加元素
set.add("Java");
set.add("Python");
set.add("C++");
set.add("Java");      // 重复元素,不会添加
// 结果:[Java, Python, C++](顺序不确定)

// 3. 删除元素
set.remove("Python");
// 结果:[Java, C++]

// 4. 查询元素
boolean exists = set.contains("Java");  // true
int size = set.size();                  // 2

// 5. 遍历元素
// 方式1:增强for循环
for (String item : set) {
    System.out.println(item);
}

// 方式2:Iterator迭代器
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

// 方式3:forEach + Lambda
set.forEach(item -> System.out.println(item));

// 方式4:Stream API
set.stream().forEach(System.out::println);

// 6. 集合运算
Set<Integer> set1 = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
Set<Integer> set2 = new HashSet<>(Arrays.asList(4, 5, 6, 7, 8));

// 并集
Set<Integer> union = new HashSet<>(set1);
union.addAll(set2);
// 结果:[1, 2, 3, 4, 5, 6, 7, 8]

// 交集
Set<Integer> intersection = new HashSet<>(set1);
intersection.retainAll(set2);
// 结果:[4, 5]

// 差集
Set<Integer> difference = new HashSet<>(set1);
difference.removeAll(set2);
// 结果:[1, 2, 3]

自定义对象的去重:

java 复制代码
// 自定义Student类
public class Student {
    private String name;
    private int age;
    
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // 必须重写hashCode和equals方法
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Student student = (Student) obj;
        return age == student.age && Objects.equals(name, student.name);
    }
}

// 使用HashSet存储Student
Set<Student> students = new HashSet<>();
students.add(new Student("张三", 20));
students.add(new Student("李四", 21));
students.add(new Student("张三", 20));  // 重复,不会添加
// 结果:只有2个学生

HashSet的使用场景:

java 复制代码
// ✓ 适合场景1:数据去重
List<String> list = Arrays.asList("A", "B", "A", "C", "B");
Set<String> uniqueSet = new HashSet<>(list);
// 结果:[A, B, C]

// ✓ 适合场景2:快速查找
Set<String> blacklist = new HashSet<>(Arrays.asList("user1", "user2"));
if (blacklist.contains(username)) {
    // 用户在黑名单中,O(1)时间复杂度
}

// ✓ 适合场景3:集合运算
Set<String> tags1 = new HashSet<>(Arrays.asList("Java", "Spring", "MySQL"));
Set<String> tags2 = new HashSet<>(Arrays.asList("Python", "Django", "MySQL"));
// 找出共同标签
tags1.retainAll(tags2);  // [MySQL]

3.3 TreeSet详解

TreeSet的核心特点:

  • 底层使用红黑树实现(TreeMap)
  • 有序:元素按照自然顺序或自定义顺序排序
  • 不可重复:通过compareTo或Comparator判断
  • 不允许null值
  • 非线程安全
  • 查询:O(log n)时间复杂度

TreeSet的排序方式:

dart 复制代码
TreeSet的排序
    ↓
方式1:自然排序(Comparable)
    - 元素类实现Comparable接口
    - 重写compareTo方法
    
方式2:定制排序(Comparator)
    - 创建TreeSet时传入Comparator
    - 实现compare方法

TreeSet的基本使用:

java 复制代码
// 1. 自然排序(数字、字符串)
Set<Integer> numbers = new TreeSet<>();
numbers.add(5);
numbers.add(2);
numbers.add(8);
numbers.add(1);
// 结果:[1, 2, 5, 8](自动排序)

Set<String> names = new TreeSet<>();
names.add("张三");
names.add("李四");
names.add("王五");
// 结果:按字典序排序

// 2. 自定义排序(Comparator)
// 降序排列
Set<Integer> descSet = new TreeSet<>((a, b) -> b - a);
descSet.add(5);
descSet.add(2);
descSet.add(8);
// 结果:[8, 5, 2]

// 按字符串长度排序
Set<String> lengthSet = new TreeSet<>((s1, s2) -> {
    int lenCompare = Integer.compare(s1.length(), s2.length());
    return lenCompare != 0 ? lenCompare : s1.compareTo(s2);
});
lengthSet.add("Java");
lengthSet.add("C");
lengthSet.add("Python");
// 结果:[C, Java, Python]

// 3. 自定义对象排序
public class Student implements Comparable<Student> {
    private String name;
    private int score;
    
    @Override
    public int compareTo(Student other) {
        // 按分数降序排列
        return Integer.compare(other.score, this.score);
    }
}

Set<Student> students = new TreeSet<>();
students.add(new Student("张三", 85));
students.add(new Student("李四", 92));
students.add(new Student("王五", 78));
// 结果:按分数降序排列

// 4. TreeSet的范围操作
TreeSet<Integer> set = new TreeSet<>(Arrays.asList(1, 3, 5, 7, 9));

// 获取子集
SortedSet<Integer> subSet = set.subSet(3, 8);  // [3, 5, 7]
SortedSet<Integer> headSet = set.headSet(5);   // [1, 3]
SortedSet<Integer> tailSet = set.tailSet(5);   // [5, 7, 9]

// 获取边界元素
Integer first = set.first();   // 1
Integer last = set.last();     // 9

// 获取相邻元素
Integer lower = set.lower(5);   // 3(小于5的最大元素)
Integer higher = set.higher(5); // 7(大于5的最小元素)
Integer floor = set.floor(6);   // 5(小于等于6的最大元素)
Integer ceiling = set.ceiling(6); // 7(大于等于6的最小元素)

TreeSet vs HashSet对比:

维度 HashSet TreeSet
底层结构 HashMap 红黑树
是否有序 无序 有序
时间复杂度 O(1) O(log n)
是否允许null 允许 不允许
比较方式 hashCode + equals compareTo/Comparator
适用场景 去重、快速查找 需要排序

3.4 LinkedHashSet详解

LinkedHashSet的核心特点:

  • 底层使用HashMap + 双向链表实现
  • 有序:按照插入顺序排列
  • 不可重复:通过hashCode和equals判断
  • 允许null值
  • 非线程安全

LinkedHashSet的使用:

java 复制代码
// LinkedHashSet保持插入顺序
Set<String> set = new LinkedHashSet<>();
set.add("Java");
set.add("Python");
set.add("C++");
set.add("Go");
// 结果:[Java, Python, C++, Go](保持插入顺序)

// 对比HashSet(无序)
Set<String> hashSet = new HashSet<>();
hashSet.add("Java");
hashSet.add("Python");
hashSet.add("C++");
hashSet.add("Go");
// 结果:顺序不确定

3.5 Set的选择决策树

javascript 复制代码
需要Set集合
    ↓
是否需要排序?
    ├─ 是 → TreeSet(自动排序)
    │
    └─ 否 → 是否需要保持插入顺序?
            ├─ 是 → LinkedHashSet(插入顺序)
            │
            └─ 否 → HashSet(无序,性能最好)

四、Map集合详解

4.1 Map接口概述

Map的核心特点:

  • 键值对存储:每个元素包含key和value
  • key唯一:不允许重复的key
  • value可重复:允许重复的value
  • key-value一一对应:一个key对应一个value

Map接口的常用方法:

java 复制代码
public interface Map<K, V> {
    // 添加/修改
    V put(K key, V value);              // 添加键值对,返回旧值
    void putAll(Map<? extends K, ? extends V> m);  // 批量添加
    V putIfAbsent(K key, V value);      // key不存在时才添加
    
    // 删除
    V remove(Object key);               // 删除指定key
    boolean remove(Object key, Object value);  // key和value都匹配才删除
    
    // 查询
    V get(Object key);                  // 获取value
    V getOrDefault(Object key, V defaultValue);  // 获取value,不存在返回默认值
    boolean containsKey(Object key);    // 判断是否包含key
    boolean containsValue(Object value); // 判断是否包含value
    int size();                         // 获取键值对个数
    boolean isEmpty();                  // 判断是否为空
    
    // 批量操作
    void clear();                       // 清空Map
    Set<K> keySet();                    // 获取所有key的Set
    Collection<V> values();             // 获取所有value的Collection
    Set<Map.Entry<K, V>> entrySet();    // 获取所有键值对的Set
    
    // Java 8新增方法
    V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction);
    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);
    void forEach(BiConsumer<? super K, ? super V> action);
    void replaceAll(BiFunction<? super K, ? super V, ? extends V> function);
}

4.2 HashMap详解

HashMap的核心特点:

  • 底层使用数组 + 链表 + 红黑树实现
  • 无序:不保证key的顺序
  • key和value都允许null
  • 非线程安全
  • 查询快:O(1)时间复杂度(理想情况)

HashMap的底层原理:

yaml 复制代码
HashMap内部结构(JDK 1.8):

┌─────────────────────────────────────────────────────────┐
│ HashMap                                                  │
├─────────────────────────────────────────────────────────┤
│ table: Node<K,V>[]     ← 哈希表(数组)                 │
│ size: int              ← 键值对个数                     │
│ threshold: int         ← 扩容阈值 = capacity * loadFactor │
│ loadFactor: float      ← 负载因子,默认0.75             │
│ DEFAULT_INITIAL_CAPACITY = 16  ← 默认初始容量           │
└─────────────────────────────────────────────────────────┘

Node节点结构:
┌─────────────────────────────────────────┐
│ Node<K,V>                                │
├─────────────────────────────────────────┤
│ hash: int        ← key的哈希值          │
│ key: K           ← 键                   │
│ value: V         ← 值                   │
│ next: Node<K,V>  ← 指向下一个节点       │
└─────────────────────────────────────────┘

存储结构示意图:
table数组
┌────┬────┬────┬────┬────┬────┬────┬────┐
│ 0  │ 1  │ 2  │ 3  │ 4  │ 5  │ 6  │ 7  │
└────┴────┴────┴────┴────┴────┴────┴────┘
  │    │    │    │
  ↓    ↓    ↓    ↓
 Node Node Node Node
  │              │
  ↓              ↓
 Node          红黑树
  │           (链表长度≥8)
  ↓
 Node

HashMap的put过程:

scss 复制代码
put(key, value)的执行流程:

1. 计算key的hash值
   hash = hash(key)
   
2. 根据hash值计算数组索引
   index = (table.length - 1) & hash
   
3. 判断table[index]位置
   ├─ 如果为null
   │  └─ 直接创建新Node放入
   │
   └─ 如果不为null(发生哈希冲突)
      ├─ 如果key已存在
      │  └─ 替换旧value,返回旧value
      │
      └─ 如果key不存在
         ├─ 如果是链表
         │  ├─ 链表长度 < 8
         │  │  └─ 添加到链表末尾
         │  │
         │  └─ 链表长度 ≥ 8
         │     └─ 转换为红黑树
         │
         └─ 如果是红黑树
            └─ 添加到红黑树中
            
4. 判断是否需要扩容
   if (++size > threshold)
       resize()  // 扩容为原来的2倍

HashMap的基本使用:

java 复制代码
// 1. 创建HashMap
Map<String, Integer> map = new HashMap<>();           // 默认容量16
Map<String, Integer> map2 = new HashMap<>(32);        // 指定初始容量
Map<String, Integer> map3 = new HashMap<>(map);       // 从其他Map创建

// 2. 添加键值对
map.put("Java", 95);
map.put("Python", 88);
map.put("C++", 92);
map.put("Java", 98);  // key重复,会覆盖旧值
// 结果:{Java=98, Python=88, C++=92}

// 3. 获取value
Integer score = map.get("Java");           // 98
Integer score2 = map.get("Go");            // null(key不存在)
Integer score3 = map.getOrDefault("Go", 0); // 0(返回默认值)

// 4. 判断key/value是否存在
boolean hasJava = map.containsKey("Java");      // true
boolean has100 = map.containsValue(100);        // false

// 5. 删除键值对
map.remove("Python");                      // 删除key为Python的键值对
map.remove("C++", 90);                     // key和value都匹配才删除(不会删除)
map.remove("C++", 92);                     // 删除成功

// 6. 遍历Map
// 方式1:遍历keySet
for (String key : map.keySet()) {
    Integer value = map.get(key);
    System.out.println(key + " = " + value);
}

// 方式2:遍历entrySet(推荐,性能好)
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    String key = entry.getKey();
    Integer value = entry.getValue();
    System.out.println(key + " = " + value);
}

// 方式3:遍历values
for (Integer value : map.values()) {
    System.out.println(value);
}

// 方式4:forEach + Lambda(推荐)
map.forEach((key, value) -> {
    System.out.println(key + " = " + value);
});

// 方式5:Stream API
map.entrySet().stream()
    .forEach(entry -> System.out.println(entry.getKey() + " = " + entry.getValue()));

// 7. Java 8新增方法
// putIfAbsent:key不存在时才添加
map.putIfAbsent("Go", 85);  // 添加成功
map.putIfAbsent("Java", 100); // key已存在,不添加

// computeIfAbsent:key不存在时计算并添加
map.computeIfAbsent("Rust", k -> k.length() * 10);  // Rust=40

// computeIfPresent:key存在时重新计算value
map.computeIfPresent("Java", (k, v) -> v + 5);  // Java=103

// merge:合并value
map.merge("Java", 10, (oldVal, newVal) -> oldVal + newVal);  // Java=113

// replace:替换value
map.replace("Python", 88, 90);  // 旧值匹配才替换
map.replace("Python", 95);      // 直接替换

HashMap的使用场景:

java 复制代码
// ✓ 适合场景1:缓存数据
Map<String, User> userCache = new HashMap<>();
userCache.put("user123", new User("张三"));
User user = userCache.get("user123");  // O(1)快速获取

// ✓ 适合场景2:统计频次
String text = "hello world hello java";
Map<String, Integer> wordCount = new HashMap<>();
for (String word : text.split(" ")) {
    wordCount.put(word, wordCount.getOrDefault(word, 0) + 1);
}
// 结果:{hello=2, world=1, java=1}

// ✓ 适合场景3:配置信息
Map<String, String> config = new HashMap<>();
config.put("db.host", "localhost");
config.put("db.port", "3306");
config.put("db.username", "root");

// ✓ 适合场景4:分组数据
List<Student> students = Arrays.asList(
    new Student("张三", "一班"),
    new Student("李四", "二班"),
    new Student("王五", "一班")
);
Map<String, List<Student>> groupByClass = new HashMap<>();
for (Student student : students) {
    groupByClass.computeIfAbsent(student.getClassName(), k -> new ArrayList<>())
                .add(student);
}
// 结果:{一班=[张三, 王五], 二班=[李四]}

4.3 LinkedHashMap详解

LinkedHashMap的核心特点:

  • 底层使用HashMap + 双向链表实现
  • 有序:按照插入顺序或访问顺序排列
  • 可以实现LRU缓存
  • 其他特性与HashMap相同

LinkedHashMap的使用:

java 复制代码
// 1. 按插入顺序(默认)
Map<String, Integer> map = new LinkedHashMap<>();
map.put("C", 3);
map.put("A", 1);
map.put("B", 2);
// 遍历顺序:C, A, B(保持插入顺序)

// 2. 按访问顺序(LRU)
Map<String, Integer> lruMap = new LinkedHashMap<>(16, 0.75f, true);
//                                                              ↑
//                                                         accessOrder=true
lruMap.put("A", 1);
lruMap.put("B", 2);
lruMap.put("C", 3);
lruMap.get("A");  // 访问A,A移到末尾
// 遍历顺序:B, C, A

// 3. 实现LRU缓存
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private int capacity;
    
    public LRUCache(int capacity) {
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }
    
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;  // 超过容量时删除最老的元素
    }
}

// 使用LRU缓存
LRUCache<String, String> cache = new LRUCache<>(3);
cache.put("1", "A");
cache.put("2", "B");
cache.put("3", "C");
cache.put("4", "D");  // 超过容量,删除最老的"1"
// 结果:{2=B, 3=C, 4=D}

4.4 TreeMap详解

TreeMap的核心特点:

  • 底层使用红黑树实现
  • 有序:按照key的自然顺序或自定义顺序排序
  • key不允许null,value允许null
  • 非线程安全
  • 查询:O(log n)时间复杂度

TreeMap的使用:

java 复制代码
// 1. 自然排序
Map<Integer, String> map = new TreeMap<>();
map.put(3, "C");
map.put(1, "A");
map.put(2, "B");
// 遍历顺序:1=A, 2=B, 3=C(按key排序)

// 2. 自定义排序
Map<String, Integer> scoreMap = new TreeMap<>((s1, s2) -> s2.compareTo(s1));
scoreMap.put("张三", 85);
scoreMap.put("李四", 92);
scoreMap.put("王五", 78);
// 按key降序排列

// 3. 范围操作
TreeMap<Integer, String> treeMap = new TreeMap<>();
treeMap.put(1, "A");
treeMap.put(3, "C");
treeMap.put(5, "E");
treeMap.put(7, "G");
treeMap.put(9, "I");

// 获取子Map
SortedMap<Integer, String> subMap = treeMap.subMap(3, 8);  // {3=C, 5=E, 7=G}
SortedMap<Integer, String> headMap = treeMap.headMap(5);   // {1=A, 3=C}
SortedMap<Integer, String> tailMap = treeMap.tailMap(5);   // {5=E, 7=G, 9=I}

// 获取边界key
Integer firstKey = treeMap.firstKey();   // 1
Integer lastKey = treeMap.lastKey();     // 9

// 获取相邻key
Integer lowerKey = treeMap.lowerKey(5);   // 3
Integer higherKey = treeMap.higherKey(5); // 7

4.5 Hashtable详解

Hashtable的核心特点:

  • 底层使用数组 + 链表实现
  • 线程安全:所有方法都加了synchronized
  • key和value都不允许null
  • 已过时,不推荐使用

Hashtable vs HashMap:

维度 HashMap Hashtable
线程安全 否 是
null值 允许 不允许
性能 快 慢
推荐使用 是 否

替代方案:

java 复制代码
// 不推荐:Hashtable
Hashtable<String, Integer> table = new Hashtable<>();

// 推荐方案1:Collections.synchronizedMap
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());

// 推荐方案2:ConcurrentHashMap(性能更好)
Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();

4.6 Map的选择决策树

javascript 复制代码
需要Map集合
    ↓
是否需要线程安全?
    ├─ 是 → ConcurrentHashMap(高并发)
    │
    └─ 否 → 是否需要排序?
            ├─ 是 → TreeMap(按key排序)
            │
            └─ 否 → 是否需要保持插入顺序?
                    ├─ 是 → LinkedHashMap(插入顺序)
                    │
                    └─ 否 → HashMap(无序,性能最好)

五、线程安全集合

5.1 为什么需要线程安全集合?

多线程环境下的问题:

java 复制代码
// 问题示例:多线程操作ArrayList
List<Integer> list = new ArrayList<>();

// 线程1
new Thread(() -> {
    for (int i = 0; i < 1000; i++) {
        list.add(i);
    }
}).start();

// 线程2
new Thread(() -> {
    for (int i = 1000; i < 2000; i++) {
        list.add(i);
    }
}).start();

// 可能出现的问题:
// 1. 数据丢失
// 2. ArrayIndexOutOfBoundsException
// 3. 结果不确定

5.2 线程安全集合的实现方式

方式1:使用Collections工具类

java 复制代码
// 同步List
List<String> syncList = Collections.synchronizedList(new ArrayList<>());

// 同步Set
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());

// 同步Map
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());

// 注意:遍历时仍需手动同步
synchronized (syncList) {
    for (String item : syncList) {
        System.out.println(item);
    }
}

方式2:使用并发包中的集合

java 复制代码
// CopyOnWriteArrayList:读多写少
List<String> cowList = new CopyOnWriteArrayList<>();

// CopyOnWriteArraySet:读多写少
Set<String> cowSet = new CopyOnWriteArraySet<>();

// ConcurrentHashMap:高并发
Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();

// ConcurrentSkipListMap:有序 + 高并发
Map<String, Integer> skipListMap = new ConcurrentSkipListMap<>();

5.3 CopyOnWriteArrayList详解

核心原理:

  • 写时复制(Copy-On-Write)
  • 读操作不加锁,写操作加锁并复制数组
  • 适合读多写少的场景
java 复制代码
// CopyOnWriteArrayList的使用
List<String> list = new CopyOnWriteArrayList<>();

// 写操作(加锁)
list.add("A");  // 复制数组,添加元素,替换原数组

// 读操作(不加锁)
String item = list.get(0);  // 直接读取,无锁

// 适用场景:监听器列表
List<EventListener> listeners = new CopyOnWriteArrayList<>();
listeners.add(new EventListener());  // 添加监听器(写少)
// 触发事件时遍历监听器(读多)
for (EventListener listener : listeners) {
    listener.onEvent();
}

5.4 ConcurrentHashMap详解

核心原理(JDK 1.8):

  • 使用CAS + synchronized实现
  • 分段锁机制,提高并发度
  • 读操作无锁,写操作锁定单个节点
java 复制代码
// ConcurrentHashMap的使用
Map<String, Integer> map = new ConcurrentHashMap<>();

// 线程安全的操作
map.put("key", 1);
Integer value = map.get("key");

// 原子操作
map.putIfAbsent("key", 2);  // key不存在时才添加
map.computeIfAbsent("key", k -> k.length());  // 原子计算

// 适用场景:高并发缓存
ConcurrentHashMap<String, User> userCache = new ConcurrentHashMap<>();
// 多线程安全地读写缓存
userCache.put("user123", new User());
User user = userCache.get("user123");

5.5 线程安全集合的选择

场景 推荐集合 原因
读多写少的List CopyOnWriteArrayList 读操作无锁,性能好
高并发的Map ConcurrentHashMap 分段锁,并发度高
需要排序的并发Map ConcurrentSkipListMap 有序 + 线程安全
简单的线程安全需求 Collections.synchronizedXxx 实现简单

六、集合工具类

6.1 Collections工具类

Collections提供的常用方法:

java 复制代码
// 1. 排序
List<Integer> list = new ArrayList<>(Arrays.asList(3, 1, 4, 1, 5, 9));
Collections.sort(list);  // 升序排序:[1, 1, 3, 4, 5, 9]
Collections.sort(list, Collections.reverseOrder());  // 降序:[9, 5, 4, 3, 1, 1]

// 2. 查找
int index = Collections.binarySearch(list, 4);  // 二分查找:2
int max = Collections.max(list);  // 最大值:9
int min = Collections.min(list);  // 最小值:1

// 3. 反转
Collections.reverse(list);  // 反转列表

// 4. 打乱
Collections.shuffle(list);  // 随机打乱

// 5. 填充
Collections.fill(list, 0);  // 用0填充所有元素

// 6. 复制
List<Integer> dest = new ArrayList<>(Collections.nCopies(list.size(), 0));
Collections.copy(dest, list);  // 复制list到dest

// 7. 替换
Collections.replaceAll(list, 1, 10);  // 将所有1替换为10

// 8. 频次统计
int frequency = Collections.frequency(list, 1);  // 统计1出现的次数

// 9. 不可变集合
List<String> immutableList = Collections.unmodifiableList(list);
// immutableList.add("X");  // 抛出UnsupportedOperationException

// 10. 空集合
List<String> emptyList = Collections.emptyList();
Set<String> emptySet = Collections.emptySet();
Map<String, Integer> emptyMap = Collections.emptyMap();

// 11. 单元素集合
List<String> singletonList = Collections.singletonList("A");
Set<String> singletonSet = Collections.singleton("A");
Map<String, Integer> singletonMap = Collections.singletonMap("key", 1);

6.2 Arrays工具类

Arrays提供的常用方法:

java 复制代码
// 1. 数组转List
String[] array = {"A", "B", "C"};
List<String> list = Arrays.asList(array);
// 注意:返回的List是固定大小的,不能add/remove

// 2. 排序
int[] numbers = {3, 1, 4, 1, 5, 9};
Arrays.sort(numbers);  // 升序排序

// 3. 二分查找
int index = Arrays.binarySearch(numbers, 4);  // 查找4的位置

// 4. 填充
Arrays.fill(numbers, 0);  // 用0填充数组

// 5. 复制
int[] copy = Arrays.copyOf(numbers, numbers.length);  // 复制数组
int[] range = Arrays.copyOfRange(numbers, 0, 3);  // 复制指定范围

// 6. 比较
boolean equals = Arrays.equals(numbers, copy);  // 比较两个数组

// 7. 转字符串
String str = Arrays.toString(numbers);  // [0, 0, 0, 0, 0, 0]

// 8. Stream操作
int sum = Arrays.stream(numbers).sum();  // 求和
double average = Arrays.stream(numbers).average().orElse(0);  // 平均值

七、集合的遍历方式对比

7.1 List的遍历方式

java 复制代码
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D", "E"));

// 方式1:普通for循环(推荐用于需要索引的场景)
for (int i = 0; i < list.size(); i++) {
    String item = list.get(i);
    System.out.println(i + ": " + item);
}

// 方式2:增强for循环(推荐用于简单遍历)
for (String item : list) {
    System.out.println(item);
}

// 方式3:Iterator迭代器(推荐用于需要删除元素的场景)
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String item = iterator.next();
    if (item.equals("B")) {
        iterator.remove();  // 安全删除
    }
}

// 方式4:forEach + Lambda(推荐用于简洁代码)
list.forEach(item -> System.out.println(item));
list.forEach(System.out::println);  // 方法引用

// 方式5:Stream API(推荐用于复杂操作)
list.stream()
    .filter(item -> item.length() > 1)
    .map(String::toLowerCase)
    .forEach(System.out::println);

7.2 Map的遍历方式

java 复制代码
Map<String, Integer> map = new HashMap<>();
map.put("Java", 95);
map.put("Python", 88);
map.put("C++", 92);

// 方式1:遍历keySet
for (String key : map.keySet()) {
    Integer value = map.get(key);
    System.out.println(key + " = " + value);
}

// 方式2:遍历entrySet(推荐,性能最好)
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    String key = entry.getKey();
    Integer value = entry.getValue();
    System.out.println(key + " = " + value);
}

// 方式3:遍历values
for (Integer value : map.values()) {
    System.out.println(value);
}

// 方式4:forEach + Lambda(推荐)
map.forEach((key, value) -> {
    System.out.println(key + " = " + value);
});

// 方式5:Stream API
map.entrySet().stream()
    .filter(entry -> entry.getValue() > 90)
    .forEach(entry -> System.out.println(entry.getKey()));

7.3 遍历时删除元素

java 复制代码
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));

// ✗ 错误方式1:增强for循环中删除
for (String item : list) {
    if (item.equals("B")) {
        list.remove(item);  // ConcurrentModificationException
    }
}

// ✗ 错误方式2:普通for循环中删除(索引问题)
for (int i = 0; i < list.size(); i++) {
    if (list.get(i).equals("B")) {
        list.remove(i);  // 删除后索引变化,可能跳过元素
    }
}

// ✓ 正确方式1:使用Iterator
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String item = iterator.next();
    if (item.equals("B")) {
        iterator.remove();  // 安全删除
    }
}

// ✓ 正确方式2:倒序遍历
for (int i = list.size() - 1; i >= 0; i--) {
    if (list.get(i).equals("B")) {
        list.remove(i);
    }
}

// ✓ 正确方式3:removeIf(Java 8)
list.removeIf(item -> item.equals("B"));

// ✓ 正确方式4:Stream过滤(创建新集合)
List<String> filtered = list.stream()
    .filter(item -> !item.equals("B"))
    .collect(Collectors.toList());

八、集合的性能对比

8.1 时间复杂度对比表

List实现类:

操作 ArrayList LinkedList Vector
get(index) O(1) O(n) O(1)
add(e) O(1)* O(1) O(1)*
add(index, e) O(n) O(1)** O(n)
remove(index) O(n) O(1)** O(n)
contains(e) O(n) O(n) O(n)

*均摊时间复杂度

**已知节点位置时

Set实现类:

操作 HashSet TreeSet LinkedHashSet
add(e) O(1) O(log n) O(1)
remove(e) O(1) O(log n) O(1)
contains(e) O(1) O(log n) O(1)

Map实现类:

操作 HashMap TreeMap LinkedHashMap
put(k, v) O(1) O(log n) O(1)
get(k) O(1) O(log n) O(1)
remove(k) O(1) O(log n) O(1)
containsKey(k) O(1) O(log n) O(1)

8.2 内存占用对比

diff 复制代码
内存占用(从小到大):

ArrayList < LinkedList
- ArrayList:只存储元素
- LinkedList:存储元素 + 前后指针

HashSet < TreeSet < LinkedHashSet
- HashSet:HashMap的key
- TreeSet:红黑树节点
- LinkedHashSet:HashMap + 双向链表

HashMap < TreeMap < LinkedHashMap
- HashMap:数组 + 链表/红黑树
- TreeMap:红黑树节点
- LinkedHashMap:HashMap + 双向链表

8.3 性能测试示例

java 复制代码
public class CollectionPerformanceTest {
    
    public static void main(String[] args) {
        int size = 100000;
        
        // 测试ArrayList vs LinkedList
        testListAdd(size);
        testListGet(size);
        testListRemove(size);
        
        // 测试HashSet vs TreeSet
        testSetAdd(size);
        testSetContains(size);
        
        // 测试HashMap vs TreeMap
        testMapPut(size);
        testMapGet(size);
    }
    
    // ArrayList vs LinkedList:添加元素
    private static void testListAdd(int size) {
        // ArrayList在末尾添加
        long start = System.currentTimeMillis();
        List<Integer> arrayList = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            arrayList.add(i);
        }
        System.out.println("ArrayList add: " + (System.currentTimeMillis() - start) + "ms");
        
        // LinkedList在末尾添加
        start = System.currentTimeMillis();
        List<Integer> linkedList = new LinkedList<>();
        for (int i = 0; i < size; i++) {
            linkedList.add(i);
        }
        System.out.println("LinkedList add: " + (System.currentTimeMillis() - start) + "ms");
        
        // ArrayList在开头添加(性能差)
        start = System.currentTimeMillis();
        arrayList = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            arrayList.add(0, i);
        }
        System.out.println("ArrayList add(0): " + (System.currentTimeMillis() - start) + "ms");
        
        // LinkedList在开头添加(性能好)
        start = System.currentTimeMillis();
        linkedList = new LinkedList<>();
        for (int i = 0; i < 10000; i++) {
            linkedList.add(0, i);
        }
        System.out.println("LinkedList add(0): " + (System.currentTimeMillis() - start) + "ms");
    }
    
    // ArrayList vs LinkedList:随机访问
    private static void testListGet(int size) {
        List<Integer> arrayList = new ArrayList<>();
        List<Integer> linkedList = new LinkedList<>();
        for (int i = 0; i < size; i++) {
            arrayList.add(i);
            linkedList.add(i);
        }
        
        // ArrayList随机访问(性能好)
        long start = System.currentTimeMillis();
        for (int i = 0; i < size; i++) {
            arrayList.get(i);
        }
        System.out.println("ArrayList get: " + (System.currentTimeMillis() - start) + "ms");
        
        // LinkedList随机访问(性能差)
        start = System.currentTimeMillis();
        for (int i = 0; i < size; i++) {
            linkedList.get(i);
        }
        System.out.println("LinkedList get: " + (System.currentTimeMillis() - start) + "ms");
    }
}

九、集合的常见使用场景

9.1 数据去重

java 复制代码
// 场景:从列表中去除重复元素
List<String> list = Arrays.asList("A", "B", "A", "C", "B", "D");

// 方式1:使用HashSet
Set<String> set = new HashSet<>(list);
List<String> uniqueList = new ArrayList<>(set);
// 结果:[A, B, C, D](无序)

// 方式2:使用LinkedHashSet(保持顺序)
Set<String> linkedSet = new LinkedHashSet<>(list);
List<String> orderedUniqueList = new ArrayList<>(linkedSet);
// 结果:[A, B, C, D](保持插入顺序)

// 方式3:使用Stream
List<String> streamUnique = list.stream()
    .distinct()
    .collect(Collectors.toList());

9.2 统计频次

java 复制代码
// 场景:统计单词出现次数
String text = "hello world hello java world hello";
String[] words = text.split(" ");

// 方式1:使用HashMap
Map<String, Integer> wordCount = new HashMap<>();
for (String word : words) {
    wordCount.put(word, wordCount.getOrDefault(word, 0) + 1);
}
// 结果:{hello=3, world=2, java=1}

// 方式2:使用computeIfAbsent
Map<String, Integer> wordCount2 = new HashMap<>();
for (String word : words) {
    wordCount2.merge(word, 1, Integer::sum);
}

// 方式3:使用Stream
Map<String, Long> wordCount3 = Arrays.stream(words)
    .collect(Collectors.groupingBy(w -> w, Collectors.counting()));

9.3 分组数据

java 复制代码
// 场景:按年龄分组学生
List<Student> students = Arrays.asList(
    new Student("张三", 20),
    new Student("李四", 21),
    new Student("王五", 20),
    new Student("赵六", 21)
);

// 方式1:使用HashMap
Map<Integer, List<Student>> groupByAge = new HashMap<>();
for (Student student : students) {
    groupByAge.computeIfAbsent(student.getAge(), k -> new ArrayList<>())
              .add(student);
}
// 结果:{20=[张三, 王五], 21=[李四, 赵六]}

// 方式2:使用Stream
Map<Integer, List<Student>> groupByAge2 = students.stream()
    .collect(Collectors.groupingBy(Student::getAge));

9.4 排序和过滤

java 复制代码
// 场景:筛选并排序商品
List<Product> products = Arrays.asList(
    new Product("手机", 3999),
    new Product("电脑", 5999),
    new Product("平板", 2999),
    new Product("耳机", 299)
);

// 筛选价格大于1000的商品,按价格降序排列
List<Product> filtered = products.stream()
    .filter(p -> p.getPrice() > 1000)
    .sorted((p1, p2) -> Double.compare(p2.getPrice(), p1.getPrice()))
    .collect(Collectors.toList());
// 结果:[电脑(5999), 手机(3999), 平板(2999)]

9.5 数据转换

java 复制代码
// 场景:提取用户ID列表
List<User> users = Arrays.asList(
    new User(1, "张三"),
    new User(2, "李四"),
    new User(3, "王五")
);

// 方式1:传统方式
List<Integer> userIds = new ArrayList<>();
for (User user : users) {
    userIds.add(user.getId());
}

// 方式2:使用Stream
List<Integer> userIds2 = users.stream()
    .map(User::getId)
    .collect(Collectors.toList());

// 转换为Map
Map<Integer, String> userMap = users.stream()
    .collect(Collectors.toMap(User::getId, User::getName));
// 结果:{1=张三, 2=李四, 3=王五}

9.6 缓存实现

java 复制代码
// 场景:实现简单的缓存
public class SimpleCache<K, V> {
    private Map<K, V> cache = new HashMap<>();
    
    public V get(K key) {
        return cache.get(key);
    }
    
    public void put(K key, V value) {
        cache.put(key, value);
    }
    
    public boolean containsKey(K key) {
        return cache.containsKey(key);
    }
    
    public void clear() {
        cache.clear();
    }
}

// 使用缓存
SimpleCache<String, User> userCache = new SimpleCache<>();
userCache.put("user123", new User("张三"));
User user = userCache.get("user123");

9.7 队列和栈

java 复制代码
// 场景1:任务队列(先进先出)
Queue<Task> taskQueue = new LinkedList<>();
taskQueue.offer(new Task("任务1"));
taskQueue.offer(new Task("任务2"));
Task task = taskQueue.poll();  // 获取任务1

// 场景2:撤销操作(后进先出)
Deque<Command> undoStack = new LinkedList<>();
undoStack.push(new Command("操作1"));
undoStack.push(new Command("操作2"));
Command lastCommand = undoStack.pop();  // 撤销操作2

// 场景3:优先级队列
PriorityQueue<Task> priorityQueue = new PriorityQueue<>(
    (t1, t2) -> Integer.compare(t2.getPriority(), t1.getPriority())
);
priorityQueue.offer(new Task("低优先级", 1));
priorityQueue.offer(new Task("高优先级", 10));
Task highPriorityTask = priorityQueue.poll();  // 获取高优先级任务

十、集合使用的最佳实践

10.1 选择合适的集合类型

java 复制代码
// ✓ 正确:根据场景选择
// 需要快速随机访问
List<String> list = new ArrayList<>();

// 需要频繁在头尾插入删除
List<String> deque = new LinkedList<>();

// 需要去重
Set<String> set = new HashSet<>();

// 需要排序
Set<String> sortedSet = new TreeSet<>();

// 需要键值对存储
Map<String, Integer> map = new HashMap<>();

// ✗ 错误:不考虑场景
// 频繁随机访问却使用LinkedList
List<String> badList = new LinkedList<>();
for (int i = 0; i < 10000; i++) {
    badList.get(i);  // 性能差
}

10.2 指定初始容量

java 复制代码
// ✓ 正确:已知大小时指定初始容量
List<String> list = new ArrayList<>(1000);
Map<String, Integer> map = new HashMap<>(1000);
// 避免频繁扩容,提升性能

// ✗ 错误:大量数据使用默认容量
List<String> badList = new ArrayList<>();  // 默认10
for (int i = 0; i < 10000; i++) {
    badList.add("item" + i);  // 多次扩容,性能差
}

10.3 使用泛型

java 复制代码
// ✓ 正确:使用泛型,类型安全
List<String> list = new ArrayList<>();
list.add("Java");
String item = list.get(0);  // 不需要强制转换

// ✗ 错误:不使用泛型
List rawList = new ArrayList();
rawList.add("Java");
rawList.add(123);  // 可以添加任意类型,不安全
String item = (String) rawList.get(0);  // 需要强制转换

10.4 避免在循环中修改集合

java 复制代码
// ✗ 错误:在增强for循环中删除元素
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String item : list) {
    if (item.equals("B")) {
        list.remove(item);  // ConcurrentModificationException
    }
}

// ✓ 正确:使用Iterator
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String item = iterator.next();
    if (item.equals("B")) {
        iterator.remove();
    }
}

// ✓ 正确:使用removeIf
list.removeIf(item -> item.equals("B"));

10.5 使用不可变集合

java 复制代码
// 场景:常量集合,防止被修改
// 方式1:Collections.unmodifiableXxx
List<String> list = Arrays.asList("A", "B", "C");
List<String> immutableList = Collections.unmodifiableList(list);
// immutableList.add("D");  // UnsupportedOperationException

// 方式2:Java 9+ List.of
List<String> immutableList2 = List.of("A", "B", "C");
Set<String> immutableSet = Set.of("A", "B", "C");
Map<String, Integer> immutableMap = Map.of("A", 1, "B", 2);

// 方式3:Guava ImmutableXxx
ImmutableList<String> guavaList = ImmutableList.of("A", "B", "C");
ImmutableSet<String> guavaSet = ImmutableSet.of("A", "B", "C");
ImmutableMap<String, Integer> guavaMap = ImmutableMap.of("A", 1, "B", 2);

10.6 注意equals和hashCode

java 复制代码
// 自定义对象作为HashMap的key或HashSet的元素时
// 必须重写equals和hashCode方法
public class Student {
    private String name;
    private int age;
    
    @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(name, student.name);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

// 使用
Set<Student> students = new HashSet<>();
students.add(new Student("张三", 20));
students.add(new Student("张三", 20));  // 正确去重

10.7 使用Stream API简化代码

java 复制代码
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// ✗ 传统方式:代码冗长
List<Integer> result = new ArrayList<>();
for (Integer num : numbers) {
    if (num % 2 == 0) {
        int square = num * num;
        result.add(square);
    }
}

// ✓ Stream方式:代码简洁
List<Integer> result2 = numbers.stream()
    .filter(n -> n % 2 == 0)
    .map(n -> n * n)
    .collect(Collectors.toList());

十一、集合API速查表

11.1 List常用方法速查

scss 复制代码
┌────────────────────────────────────────────────────────────────┐
│                      List常用方法                               │
├────────────────────────────────────────────────────────────────┤
│ add(E e)                    在末尾添加元素                     │
│ add(int index, E element)   在指定位置插入元素                 │
│ get(int index)              获取指定位置的元素                 │
│ set(int index, E element)   替换指定位置的元素                 │
│ remove(int index)           删除指定位置的元素                 │
│ remove(Object o)            删除指定元素                       │
│ size()                      获取元素个数                       │
│ isEmpty()                   判断是否为空                       │
│ contains(Object o)          判断是否包含某元素                 │
│ indexOf(Object o)           查找元素第一次出现的位置           │
│ clear()                     清空集合                           │
│ subList(int from, int to)   获取子列表                         │
└────────────────────────────────────────────────────────────────┘

11.2 Set常用方法速查

scss 复制代码
┌────────────────────────────────────────────────────────────────┐
│                      Set常用方法                                │
├────────────────────────────────────────────────────────────────┤
│ add(E e)                    添加元素(重复返回false)          │
│ remove(Object o)            删除元素                           │
│ contains(Object o)          判断是否包含某元素                 │
│ size()                      获取元素个数                       │
│ isEmpty()                   判断是否为空                       │
│ clear()                     清空集合                           │
│ addAll(Collection c)        添加另一个集合的所有元素(并集)   │
│ retainAll(Collection c)     保留两个集合的交集                 │
│ removeAll(Collection c)     删除另一个集合的所有元素(差集)   │
└────────────────────────────────────────────────────────────────┘

11.3 Map常用方法速查

scss 复制代码
┌────────────────────────────────────────────────────────────────┐
│                      Map常用方法                                │
├────────────────────────────────────────────────────────────────┤
│ put(K key, V value)         添加/更新键值对                    │
│ get(Object key)             获取value                          │
│ getOrDefault(K key, V def)  获取value,不存在返回默认值        │
│ remove(Object key)          删除键值对                         │
│ containsKey(Object key)     判断是否包含key                    │
│ containsValue(Object value) 判断是否包含value                  │
│ size()                      获取键值对个数                     │
│ isEmpty()                   判断是否为空                       │
│ clear()                     清空Map                            │
│ keySet()                    获取所有key的Set                   │
│ values()                    获取所有value的Collection          │
│ entrySet()                  获取所有键值对的Set                │
│ putIfAbsent(K key, V value) key不存在时才添加                  │
│ computeIfAbsent(K key, F)   key不存在时计算并添加              │
│ merge(K key, V value, F)    合并value                          │
└────────────────────────────────────────────────────────────────┘

十二、练习题

12.1 基础练习

练习1:List基本操作

java 复制代码
// 创建一个ArrayList,添加5个学生姓名
// 要求:
// 1. 在索引2处插入一个新学生
// 2. 删除第一个学生
// 3. 修改最后一个学生的姓名
// 4. 遍历并打印所有学生

练习2:Set去重

java 复制代码
// 给定一个包含重复元素的List
List<Integer> numbers = Arrays.asList(1, 2, 3, 2, 4, 3, 5, 1);
// 要求:使用Set去重,并保持原有顺序

练习3:Map统计

java 复制代码
// 统计字符串中每个字符出现的次数
String text = "hello world";
// 要求:使用HashMap统计,输出每个字符及其出现次数

12.2 进阶练习

练习4:自定义对象排序

java 复制代码
// 创建Student类,包含name和score字段
// 要求:
// 1. 创建一个List存储多个学生
// 2. 按分数降序排序
// 3. 分数相同时按姓名升序排序

练习5:集合转换

java 复制代码
// 给定一个List<User>,User包含id和name字段
// 要求:转换为Map<Integer, String>,key为id,value为name

练习6:数据分组

java 复制代码
// 给定一个List<Product>,Product包含category和price字段
// 要求:按category分组,计算每个分类的平均价格

12.3 综合练习

练习7:实现LRU缓存

java 复制代码
// 要求:
// 1. 继承LinkedHashMap实现LRU缓存
// 2. 指定最大容量
// 3. 超过容量时自动删除最久未使用的元素
// 4. 提供get和put方法

练习8:多线程安全的计数器

java 复制代码
// 要求:
// 1. 使用ConcurrentHashMap实现线程安全的计数器
// 2. 提供increment方法增加计数
// 3. 提供getCount方法获取计数
// 4. 多线程环境下测试正确性

十三、学习检查清单

  • 理解Java集合框架的整体架构
  • 掌握List、Set、Map三大体系的特点
  • 熟练使用ArrayList和LinkedList
  • 理解ArrayList的扩容机制
  • 掌握HashSet的去重原理
  • 理解HashMap的底层实现
  • 掌握HashMap的put和get过程
  • 了解TreeSet和TreeMap的排序机制
  • 掌握LinkedHashMap的使用场景
  • 理解线程安全集合的实现方式
  • 熟练使用Collections工具类
  • 掌握集合的5种遍历方式
  • 能够根据场景选择合适的集合类型
  • 理解集合的性能特点
  • 掌握集合使用的最佳实践
  • 能够解决集合相关的常见问题
  • 熟练使用Stream API操作集合
  • 理解ConcurrentHashMap的原理
  • 掌握自定义对象的equals和hashCode
  • 能够实现常见的集合应用场景

十四、常见面试题

14.1 ArrayList vs LinkedList

Q: ArrayList和LinkedList的区别?

A:

  1. 底层结构:ArrayList使用动态数组,LinkedList使用双向链表
  2. 随机访问:ArrayList O(1),LinkedList O(n)
  3. 插入删除:ArrayList O(n),LinkedList O(1)(已知节点)
  4. 内存占用:ArrayList连续内存,LinkedList额外存储指针
  5. 适用场景:ArrayList适合查询多,LinkedList适合增删多

14.2 HashMap原理

Q: HashMap的底层实现原理?

A:

  1. JDK 1.8使用数组+链表+红黑树
  2. 通过hash函数计算key的哈希值
  3. 根据哈希值确定数组索引
  4. 哈希冲突时使用链表或红黑树
  5. 链表长度≥8时转换为红黑树
  6. 负载因子0.75,超过阈值时扩容2倍

14.3 ConcurrentHashMap

Q: ConcurrentHashMap如何实现线程安全?

A:

  1. JDK 1.7使用分段锁(Segment)
  2. JDK 1.8使用CAS + synchronized
  3. 读操作无锁,写操作锁定单个节点
  4. 提高并发度,性能优于Hashtable

14.4 fail-fast机制

Q: 什么是fail-fast机制?

A:

  1. 快速失败机制,检测并发修改
  2. 遍历时如果集合被修改,抛出ConcurrentModificationException
  3. 通过modCount字段实现
  4. 解决方案:使用Iterator.remove()或使用并发集合

十五、总结

Java集合框架是Java开发中最常用的API之一,掌握集合框架对于提高开发效率至关重要。

核心要点:

  1. 三大体系:List(有序可重复)、Set(无序不重复)、Map(键值对)
  2. 常用实现:ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap
  3. 性能特点:ArrayList查询快、LinkedList增删快、HashMap查询快
  4. 线程安全:CopyOnWriteArrayList、ConcurrentHashMap
  5. 最佳实践:选择合适的集合、指定初始容量、使用泛型、注意并发修改
相关推荐
驭渊的小故事19 分钟前
Spring AI Alibaba 实战:从maven依赖治理到声明式工具调用
java·人工智能·spring
Elastic 中国社区官方博客43 分钟前
我们如何通过 70 多个拉取请求,将 OpenTelemetry Java agent 迁移到 invokedynamic
java·大数据·开发语言·elasticsearch·搜索引擎·全文检索·es
tyn1881 小时前
记录本地安装nacos服务的方法及异常
java·开发语言
步行cgn1 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
y1su2 小时前
Leetcode 字符串模板
java·数据结构·算法·leetcode·职场和发展·动态规划
我命由我123453 小时前
Python 开发 - Python 的 pathlib
java·开发语言·后端·python·学习·java-ee·学习方法
苜蓿muxu3 小时前
Java 字符串拼接:+、StringBuilder、StringBuffer的区别
java
摇滚侠3 小时前
《Java并发编程之美》第 4 章 Java 并发包中原子操作类原理剖析 阅读笔记 7
java·笔记·python
旋律4144 小时前
同义方法区别(增加类与删除类)
java·后端