Java集合框架完全指南

0 阅读33分钟

Java集合框架完全指南

学习目标

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

一、概念引入

1.1 为什么需要集合框架?

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

场景1:用户列表管理

// ✗ 使用数组的问题
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:商品去重

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

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

场景3:配置信息存储

// ✗ 使用两个数组维护键值对
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工具类提供排序、查找等算法

集合框架的层次结构:

                    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接口的常用方法:

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的底层原理:

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.5elementData = Arrays.copyOf(elementData, newCapacity)

ArrayList的基本使用:

// 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的使用场景:

// ✓ 适合场景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的底层原理:

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的基本使用:

// 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对比:

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

LinkedList的使用场景:

// ✓ 适合场景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:

// 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的选择决策树

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

三、Set集合详解

3.1 Set接口概述

Set的核心特点:

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

Set接口的常用方法:

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的去重原理:

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

3.2 HashSet详解

HashSet的核心特点:

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

HashSet的底层原理:

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的基本使用:

// 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]

自定义对象的去重:

// 自定义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的使用场景:

// ✓ 适合场景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的排序方式:

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

TreeSet的基本使用:

// 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对比:

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

3.4 LinkedHashSet详解

LinkedHashSet的核心特点:

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

LinkedHashSet的使用:

// 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的选择决策树

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

四、Map集合详解

4.1 Map接口概述

Map的核心特点:

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

Map接口的常用方法:

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的底层原理:

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过程:

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的基本使用:

// 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的使用场景:

// ✓ 适合场景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的使用:

// 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的使用:

// 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:

维度HashMapHashtable
线程安全
null值允许不允许
性能
推荐使用

替代方案:

// 不推荐: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的选择决策树

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

五、线程安全集合

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

多线程环境下的问题:

// 问题示例:多线程操作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工具类

// 同步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:使用并发包中的集合

// 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)
  • 读操作不加锁,写操作加锁并复制数组
  • 适合读多写少的场景
// 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实现
  • 分段锁机制,提高并发度
  • 读操作无锁,写操作锁定单个节点
// 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 线程安全集合的选择

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

六、集合工具类

6.1 Collections工具类

Collections提供的常用方法:

// 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提供的常用方法:

// 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的遍历方式

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的遍历方式

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 遍历时删除元素

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实现类:

操作ArrayListLinkedListVector
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实现类:

操作HashSetTreeSetLinkedHashSet
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实现类:

操作HashMapTreeMapLinkedHashMap
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 内存占用对比

内存占用(从小到大):

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

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

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

8.3 性能测试示例

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 数据去重

// 场景:从列表中去除重复元素
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 统计频次

// 场景:统计单词出现次数
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 分组数据

// 场景:按年龄分组学生
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 排序和过滤

// 场景:筛选并排序商品
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 数据转换

// 场景:提取用户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 缓存实现

// 场景:实现简单的缓存
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 队列和栈

// 场景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 选择合适的集合类型

// ✓ 正确:根据场景选择
// 需要快速随机访问
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 指定初始容量

// ✓ 正确:已知大小时指定初始容量
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 使用泛型

// ✓ 正确:使用泛型,类型安全
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 避免在循环中修改集合

// ✗ 错误:在增强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 使用不可变集合

// 场景:常量集合,防止被修改
// 方式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

// 自定义对象作为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简化代码

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常用方法速查

┌────────────────────────────────────────────────────────────────┐
│                      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常用方法速查

┌────────────────────────────────────────────────────────────────┐
│                      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常用方法速查

┌────────────────────────────────────────────────────────────────┐
│                      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基本操作

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

练习2:Set去重

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

练习3:Map统计

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

12.2 进阶练习

练习4:自定义对象排序

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

练习5:集合转换

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

练习6:数据分组

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

12.3 综合练习

练习7:实现LRU缓存

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

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

// 要求:
// 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. 最佳实践:选择合适的集合、指定初始容量、使用泛型、注意并发修改