{T}

集合类

学习目标

  • 掌握 CollectionMap 两大体系的接口层级与职责划分
  • 理解 List / Set / Queue 的行为差异与典型实现类(ArrayList / LinkedList / HashSet / TreeSet / PriorityQueue)
  • 区分有序/无序、允许重复/去重、线程安全/非安全等维度,能正确选型
  • 理解 Iterator 迭代器的 fail-fast(快速失败)机制与 ConcurrentModificationException
  • 了解各实现类的底层数据结构与性能特征(时间复杂度)

java.util 包中提供一些集合类,这些集合类又被称为容器。提到容器不难想到数组。集合类与数组的不同之处是:数组的长度是固定的,集合的长度是可变的;数组用来存放基本类型的数据,集合用来存放对象的引用。

java.util 包中提供一些集合类,这些集合类又被称为容器。提到容器不难想到数组。集合类与数组的不同之处是:数组的长度是固定的,集合的长度是可变的;数组用来存放基本类型的数据,集合用来存放对象的引用。

常用的集合有 List 集合、Set 集合、Queue 队列和 Map 集合,其中 List 集合、Set 集合与 Queue 队列继承 Collection 接口,各接口还提供了不同的实现类:

  • List:一种有序列表的集合,例如,按索引排列的 StudentList
  • Set:一种保证没有重复元素的集合,例如,所有无重复名称的 StudentSet
  • Queue:一种先进先出的队列集合,例如,任务队列、消息队列
  • Map:一种通过键值 (key-value) 查找的映射表集合,例如,根据 Studentname 查找对应 StudentMap

集合框架体系

Java 集合框架主要由两个根接口派生而来:

图表渲染中…
  • Collection 接口:存储单个元素

    • List 接口:有序、可重复
    • Set 接口:无序、不可重复
    • Queue 接口:队列,先进先出
  • Map 接口:存储键值对

继承体系图

code
Iterable (接口)
    └── Collection (接口)
            ├── List (接口)
            │     ├── ArrayList
            │     ├── LinkedList
            │     └── Vector
            │           └── Stack
            ├── Set (接口)
            │     ├── HashSet
            │     │     └── LinkedHashSet
            │     └── TreeSet
            └── Queue (接口)
                  ├── LinkedList
                  ├── PriorityQueue
                  └── ArrayDeque

Map (接口)
    ├── HashMap
    │     └── LinkedHashMap
    ├── TreeMap
    ├── Hashtable
    │     └── Properties
    └── ConcurrentHashMap

List 集合

List 是一个有序集合,允许存储重复的元素,并且可以通过索引访问元素。它是 Collection 接口的子接口,提供了比 Collection 更丰富的操作方法。

List 接口继承自 Collection 接口,定义了一个有序的集合,支持以下特性:

  • 有序性:元素按照插入顺序存储,可以通过索引访问
  • 允许重复:可以存储多个相同的元素
  • 索引访问:支持通过索引(从 0 开始)访问元素

核心方法

List 接口扩展了 Collection 接口的方法,并添加了一些与索引相关的操作。

添加元素

方法名功能描述
boolean add(E e)在列表末尾添加一个元素
void add(int index, E element)在指定索引位置插入一个元素
boolean addAll(Collection<? extends E> c)将指定集合中的所有元素添加到列表末尾
boolean addAll(int index, Collection<? extends E> c)将指定集合中的所有元素插入到指定索引位置

删除元素

方法名功能描述
E remove(int index)移除并返回指定索引位置的元素
boolean remove(Object o)移除列表中第一个匹配的元素(如果存在)
boolean removeAll(Collection<?> c)移除列表中包含在指定集合中的所有元素
void clear()清空列表中的所有元素

修改元素

方法名功能描述
E set(int index, E element)替换指定索引位置的元素,并返回被替换的元素

查询元素

方法名功能描述
E get(int index)返回指定索引位置的元素
int indexOf(Object o)返回指定元素的第一次出现的索引(如果不存在,返回 -1)
int lastIndexOf(Object o)返回指定元素的最后一次出现的索引(如果不存在,返回 -1)
List<E> subList(int fromIndex, int toIndex)返回从 fromIndextoIndex 的子列表(左闭右开区间)

遍历集合

方法名功能描述
Iterator<E> iterator()返回一个迭代器,用于遍历列表中的元素
ListIterator<E> listIterator()返回一个双向迭代器,支持从前往后和从后往前遍历列表
ListIterator<E> listIterator(int index)返回一个从指定索引开始的双向迭代器

排序和批量操作

方法名功能描述
default void sort(Comparator<? super E> c)对列表中的元素进行排序(需要 Java 8+)。
boolean containsAll(Collection<?> c)判断列表是否包含指定集合中的所有元素。
boolean equals(Object o)比较两个列表是否相等(元素顺序和内容相同)。
int hashCode()返回列表的哈希码值。

流式操作(Java 8+)

方法名功能描述
Spliterator<E> spliterator()返回一个 Spliterator,用于并行遍历列表中的元素。
Stream<E> stream()返回一个顺序流(Stream),用于对列表进行流式操作。
Stream<E> parallelStream()返回一个并行流(Parallel Stream),用于并行处理列表中的元素。

List 接口的实现类

List 接口有多个实现类,每个实现类都有不同的特性和适用场景。

ArrayList

  • 底层实现:基于动态数组
  • 特点
    • 支持快速随机访问(通过索引访问元素的时间复杂度为 O(1))
    • 插入和删除效率较低(特别是在中间位置,时间复杂度为 O(n))
    • 非线程安全
  • 适用场景:适合频繁读取和随机访问的场景
java
import java.util.ArrayList;
import java.util.List;

public class ArrayListExample {
  public static void main(String[] args) {
    List<String> arrayList = new ArrayList<>();
    arrayList.add("Apple");
    arrayList.add("Banana");
    arrayList.add("Cherry");

    System.out.println("ArrayList: " + arrayList); // 输出: [Apple, Banana, Cherry]
    System.out.println("Element at index 1: " + arrayList.get(1)); // 输出: Banana
  }
}

ArrayList 扩容机制

  • 初始容量为 10
  • 当容量不足时,扩容为原来的 1.5 倍(int newCapacity = oldCapacity + (oldCapacity >> 1)
  • 扩容时会创建新数组,将原数组元素复制过去

LinkedList

  • 底层实现:基于双向链表
  • 特点
    • 插入和删除效率高(特别是在中间位置,时间复杂度为 O(1))
    • 随机访问效率低(通过索引访问元素的时间复杂度为 O(n))
    • 非线程安全
    • 实现了 ListDeque 接口,可以作为列表、队列、栈使用
  • 适用场景:适合频繁插入和删除的场景
java
import java.util.LinkedList;
import java.util.List;

public class LinkedListExample {
  public static void main(String[] args) {
    LinkedList<String> linkedList = new LinkedList<>();
    linkedList.add("Apple");
    linkedList.add("Banana");
    linkedList.add("Cherry");

    System.out.println("LinkedList: " + linkedList); // 输出: [Apple, Banana, Cherry]
    System.out.println("First Element: " + linkedList.getFirst()); // 输出: Apple
    System.out.println("Last Element: " + linkedList.getLast()); // 输出: Cherry
    
    // 作为栈使用
    linkedList.push("Durian"); // 等同于 addFirst
    System.out.println("Stack pop: " + linkedList.pop()); // 等同于 removeFirst
    
    // 作为队列使用
    linkedList.offer("Elderberry"); // 等同于 add/addLast
    System.out.println("Queue poll: " + linkedList.poll()); // 等同于 removeFirst
  }
}

Vector

  • 底层实现:基于动态数组
  • 特点
    • 线程安全(所有方法都使用 synchronized 修饰)
    • 性能较低,已被 ArrayList 取代
    • 扩容机制:默认扩容为原来的 2 倍
  • 适用场景:需要线程安全的场景(但推荐使用 CopyOnWriteArrayList
java
import java.util.Vector;

public class VectorExample {
  public static void main(String[] args) {
    Vector<String> vector = new Vector<>();
    vector.add("Apple");
    vector.add("Banana");
    
    System.out.println("Vector: " + vector);
    
    // Stack 是 Vector 的子类
    java.util.Stack<String> stack = new java.util.Stack<>();
    stack.push("A");
    stack.push("B");
    System.out.println("Stack pop: " + stack.pop()); // 输出: B
  }
}

CopyOnWriteArrayList

  • 底层实现:基于写时复制机制的线程安全列表
  • 特点
    • 线程安全
    • 读操作不需要加锁,性能较高
    • 写操作会复制整个数组,适合读多写少的场景
  • 适用场景:适合高并发读取、低并发写入的场景
java
import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteArrayListExample {
  public static void main(String[] args) {
    CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
    list.add("Apple");
    list.add("Banana");

    System.out.println("List: " + list); // 输出: [Apple, Banana]
    
    // 适合并发遍历
    list.forEach(System.out::println);
  }
}

常用操作示例

遍历列表

使用 for 循环
java
import java.util.ArrayList;
import java.util.List;

public class ForLoopExample {
  public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("Apple");
    list.add("Banana");

    for (int i = 0; i < list.size(); i++) {
      System.out.println(list.get(i));
    }
  }
}
使用增强 for 循环(for-each)
java
import java.util.ArrayList;
import java.util.List;

public class ForEachLoopExample {
  public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("Apple");
    list.add("Banana");

    for (String item : list) {
      System.out.println(item);
    }
  }
}
使用 forEach 方法(Java 8+)
java
import java.util.ArrayList;
import java.util.List;

public class ForEachExample {
  public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("Apple");
    list.add("Banana");

    list.forEach(System.out::println);
  }
}
使用迭代器
java
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorExample {
  public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("Apple");
    list.add("Banana");

    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
      System.out.println(iterator.next());
    }
  }
}
使用 ListIterator(双向遍历)
java
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

public class ListIteratorExample {
  public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("Apple");
    list.add("Banana");
    list.add("Cherry");

    // 正向遍历
    ListIterator<String> iterator = list.listIterator();
    while (iterator.hasNext()) {
      System.out.println("Index " + iterator.nextIndex() + ": " + iterator.next());
    }

    // 反向遍历
    System.out.println("\nReverse traversal:");
    while (iterator.hasPrevious()) {
      System.out.println("Index " + iterator.previousIndex() + ": " + iterator.previous());
    }
  }
}

排序

对列表进行排序
java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class SortExample {
  public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("Banana");
    list.add("Apple");
    list.add("Cherry");

    Collections.sort(list); // 默认按自然顺序排序
    System.out.println("Sorted List: " + list); // 输出: [Apple, Banana, Cherry]
  }
}
自定义排序
java
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class CustomSortExample {
  public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("Banana");
    list.add("Apple");
    list.add("Cherry");

    list.sort(Comparator.reverseOrder()); // 按降序排序
    System.out.println("Reverse Sorted List: " + list); // 输出: [Cherry, Banana, Apple]
  }
}

流式操作

示例:过滤和映射

java
import java.util.ArrayList;
import java.util.List;

public class StreamExample {
  public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("Apple");
    list.add("Banana");
    list.add("Cherry");

    // 过滤以 'A' 开头的元素并转换为大写
    List<String> result = list.stream()
      .filter(s -> s.startsWith("A"))
      .map(String::toUpperCase)
      .toList();

    System.out.println(result); // 输出: [APPLE]
  }
}

Queue 队列

Queue 是一个**先进先出(FIFO)**的队列集合,继承自 Collection 接口。队列通常用于存储待处理的任务或消息。

核心方法

方法名功能描述失败时行为
boolean add(E e)添加元素到队列尾部抛出异常
boolean offer(E e)添加元素到队列尾部返回 false
E remove()移除并返回队列头部元素抛出异常
E poll()移除并返回队列头部元素返回 null
E element()返回队列头部元素但不移除抛出异常
E peek()返回队列头部元素但不移除返回 null

Queue 接口的实现类

LinkedList

  • 底层实现:基于双向链表
  • 特点
    • 实现了 ListDeque 接口
    • 可以作为队列、栈、列表使用
    • 非线程安全
  • 适用场景:适合频繁插入和删除的场景
java
import java.util.LinkedList;
import java.util.Queue;

public class LinkedListQueueExample {
  public static void main(String[] args) {
    Queue<String> queue = new LinkedList<>();
    queue.offer("A");
    queue.offer("B");
    queue.offer("C");

    System.out.println("Queue: " + queue); // 输出: [A, B, C]
    System.out.println("Poll: " + queue.poll()); // 输出: A
    System.out.println("Peek: " + queue.peek()); // 输出: B
  }
}

PriorityQueue

  • 底层实现:基于堆的二叉树
  • 特点
    • 元素按优先级排序(默认自然顺序,可自定义比较器)
    • 不允许 null 元素
    • 非线程安全
  • 适用场景:需要按优先级处理元素的场景
java
import java.util.PriorityQueue;
import java.util.Queue;

public class PriorityQueueExample {
  public static void main(String[] args) {
    // 默认最小堆(元素按从小到大排序)
    Queue<Integer> queue = new PriorityQueue<>();
    queue.offer(5);
    queue.offer(1);
    queue.offer(3);

    System.out.println("PriorityQueue: " + queue);
    
    // 按优先级取出元素
    while (!queue.isEmpty()) {
      System.out.println(queue.poll());
    }
    // 输出顺序: 1, 3, 5
    
    // 自定义比较器:最大堆
    Queue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
    maxHeap.offer(5);
    maxHeap.offer(1);
    maxHeap.offer(3);
    
    System.out.println("\nMax Heap:");
    while (!maxHeap.isEmpty()) {
      System.out.println(maxHeap.poll());
    }
    // 输出顺序: 5, 3, 1
  }
}

ArrayDeque

  • 底层实现:基于循环数组
  • 特点
    • 可以作为栈和队列使用
    • 效率高于 StackLinkedList
    • 不允许 null 元素
    • 非线程安全
  • 适用场景:需要栈或队列功能的场景
java
import java.util.ArrayDeque;
import java.util.Deque;

public class ArrayDequeExample {
  public static void main(String[] args) {
    // 作为栈使用
    Deque<String> stack = new ArrayDeque<>();
    stack.push("A");
    stack.push("B");
    stack.push("C");
    
    System.out.println("Stack pop: " + stack.pop()); // 输出: C
    System.out.println("Stack peek: " + stack.peek()); // 输出: B
    
    // 作为队列使用
    Deque<String> queue = new ArrayDeque<>();
    queue.offer("A");
    queue.offer("B");
    queue.offer("C");
    
    System.out.println("\nQueue poll: " + queue.poll()); // 输出: A
    System.out.println("Queue peek: " + queue.peek()); // 输出: B
  }
}

Set 集合

Set 是一个不包含重复元素的集合,它继承自 Collection 接口。Set 接口不保证元素的顺序,但某些实现类(如 LinkedHashSet)可以维护插入顺序。

Set 接口的主要特性:

  • 无重复性:不包含重复元素,最多包含一个 null 元素
  • 无序性:不保证元素的顺序(某些实现类除外)
  • 最多一个 null 元素:大多数 Set 实现允许包含一个 null 元素

核心方法

Set 接口继承自 Collection 接口,没有添加新的方法,但重写了一些方法以确保不重复的特性。

方法名功能描述
boolean add(E e)添加元素,如果元素已存在则返回 false
boolean remove(Object o)移除指定元素,如果元素存在则返回 true
boolean contains(Object o)判断集合是否包含指定元素
int size()返回集合中的元素数量
boolean isEmpty()判断集合是否为空
Iterator<E> iterator()返回迭代器,用于遍历集合中的元素
Object[] toArray()将集合转换为数组
boolean containsAll(Collection<?> c)判断集合是否包含指定集合中的所有元素
boolean addAll(Collection<? extends E> c)将指定集合中的所有元素添加到集合中
boolean retainAll(Collection<?> c)仅保留集合中包含在指定集合中的元素
boolean removeAll(Collection<?> c)移除集合中包含在指定集合中的所有元素
void clear()移除集合中的所有元素

Set 接口的实现类

HashSet

  • 底层实现:基于哈希表(实际上是 HashMap 实例)
  • 特点
    • 不保证元素的顺序
    • 允许 null 元素
    • 非线程安全
    • 查找、插入和删除的时间复杂度都是 O(1)
  • 适用场景:需要快速查找、不关心元素顺序的场景
java
import java.util.HashSet;
import java.util.Set;

public class HashSetExample {
  public static void main(String[] args) {
    Set<String> set = new HashSet<>();
    set.add("Apple");
    set.add("Banana");
    set.add("Cherry");
    set.add("Apple"); // 重复元素,不会被添加

    System.out.println("HashSet: " + set); // 输出顺序可能不同
    System.out.println("Contains 'Apple': " + set.contains("Apple")); // 输出: true
    System.out.println("Size: " + set.size()); // 输出: 3
  }
}

HashSet 判断元素重复的原理

  1. 先调用元素的 hashCode() 方法计算哈希值
  2. 如果哈希值相同,再调用 equals() 方法比较
  3. 如果两个方法都返回 true,则认为是重复元素

LinkedHashSet

  • 底层实现:基于哈希表和链表,继承自 HashSet
  • 特点
    • 维护元素的插入顺序
    • 允许 null 元素
    • 非线程安全
    • 性能略低于 HashSet,但仍然很好
  • 适用场景:需要维护插入顺序且需要快速查找的场景
java
import java.util.LinkedHashSet;
import java.util.Set;

public class LinkedHashSetExample {
  public static void main(String[] args) {
    Set<String> set = new LinkedHashSet<>();
    set.add("Apple");
    set.add("Banana");
    set.add("Cherry");

    System.out.println("LinkedHashSet: " + set); // 输出: [Apple, Banana, Cherry],保持插入顺序
  }
}

TreeSet

  • 底层实现:基于红黑树(NavigableMap 实现)
  • 特点
    • 元素按自然顺序或自定义比较器排序
    • 不允许 null 元素
    • 非线程安全
    • 查找、插入和删除的时间复杂度都是 O(log n)
  • 适用场景:需要元素有序且需要快速查找的场景
java
import java.util.TreeSet;
import java.util.Set;

public class TreeSetExample {
  public static void main(String[] args) {
    Set<String> set = new TreeSet<>();
    set.add("Banana");
    set.add("Apple");
    set.add("Cherry");

    System.out.println("TreeSet: " + set); // 输出: [Apple, Banana, Cherry],按字母顺序排序
    
    // 自定义比较器
    Set<String> reverseSet = new TreeSet<>((a, b) -> b.compareTo(a));
    reverseSet.add("Banana");
    reverseSet.add("Apple");
    reverseSet.add("Cherry");
    
    System.out.println("Reverse TreeSet: " + reverseSet); // 输出: [Cherry, Banana, Apple]
  }
}

EnumSet

  • 底层实现:基于位向量的专用 Set 实现
  • 特点
    • 专门用于枚举类型
    • 高效且内存占用小
    • 按枚举常量的自然顺序排序
    • 非线程安全
  • 适用场景:需要存储枚举类型元素的场景
java
import java.util.EnumSet;
import java.util.Set;

public class EnumSetExample {
  enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
  
  public static void main(String[] args) {
    Set<Day> weekend = EnumSet.of(Day.SATURDAY, Day.SUNDAY);
    Set<Day> weekdays = EnumSet.range(Day.MONDAY, Day.FRIDAY);
    
    System.out.println("Weekend: " + weekend); // 输出: [SATURDAY, SUNDAY]
    System.out.println("Weekdays: " + weekdays); // 输出: [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY]
  }
}

常用操作示例

集合运算

java
import java.util.HashSet;
import java.util.Set;

public class SetOperationsExample {
  public static void main(String[] args) {
    Set<Integer> set1 = new HashSet<>();
    Set<Integer> set2 = new HashSet<>();
    
    // 添加元素
    set1.add(1);
    set1.add(2);
    set1.add(3);
    
    set2.add(2);
    set2.add(3);
    set2.add(4);
    
    // 并集
    Set<Integer> union = new HashSet<>(set1);
    union.addAll(set2);
    System.out.println("Union: " + union); // 输出: [1, 2, 3, 4]
    
    // 交集
    Set<Integer> intersection = new HashSet<>(set1);
    intersection.retainAll(set2);
    System.out.println("Intersection: " + intersection); // 输出: [2, 3]
    
    // 差集
    Set<Integer> difference = new HashSet<>(set1);
    difference.removeAll(set2);
    System.out.println("Difference: " + difference); // 输出: [1]
  }
}

自定义对象的 Set

java
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

class Student {
  private String id;
  private String name;
  
  public Student(String id, String name) {
    this.id = id;
    this.name = name;
  }
  
  // 必须重写 equals() 和 hashCode() 方法
  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Student student = (Student) o;
    return Objects.equals(id, student.id);
  }
  
  @Override
  public int hashCode() {
    return Objects.hash(id);
  }
  
  @Override
  public String toString() {
    return "Student{id='" + id + "', name='" + name + "'}";
  }
}

public class CustomObjectSetExample {
  public static void main(String[] args) {
    Set<Student> students = new HashSet<>();
    students.add(new Student("001", "张三"));
    students.add(new Student("002", "李四"));
    students.add(new Student("001", "王五")); // 不会添加,因为 id 相同
    
    System.out.println("Students: " + students);
  }
}

Map 集合

Map 是一种存储键值对(key-value)的集合,它不继承自 Collection 接口。每个键最多只能映射到一个值,键不能重复,但值可以重复。

Map 接口的主要特性:

  • 键值对存储:存储键值对,通过键访问值
  • 键唯一性:键不能重复,每个键最多映射一个值
  • 值可重复:不同的键可以映射相同的值
  • 允许一个 null 键和多个 null 值(取决于具体实现)

核心方法

方法名功能描述
V put(K key, V value)添加键值对,如果键已存在则替换旧值
V get(Object key)返回指定键映射的值,如果不存在则返回 null
V remove(Object key)移除指定键的映射关系,并返回对应的值
boolean containsKey(Object key)判断是否包含指定键
boolean containsValue(Object value)判断是否包含指定值
int size()返回键值对的数量
boolean isEmpty()判断是否为空
void clear()清空所有键值对
Set<K> keySet()返回所有键的集合
Collection<V> values()返回所有值的集合
Set<Map.Entry<K, V>> entrySet()返回所有键值对的集合
void putAll(Map<? extends K, ? extends V> m)将指定映射中的所有映射关系添加到此映射中

Java 8+ 新增方法

方法名功能描述
V getOrDefault(Object key, V defaultValue)获取指定键的值,如果不存在则返回默认值
V putIfAbsent(K key, V value)如果键不存在才添加
boolean remove(Object key, Object value)仅当键和值都匹配时才移除
boolean replace(K key, V oldValue, V newValue)仅当键和旧值都匹配时才替换
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)合并键值对

Map 接口的实现类

HashMap

  • 底层实现:基于哈希表(数组+链表/红黑树)
  • 特点
    • 不保证映射的顺序
    • 允许一个 null 键和多个 null 值
    • 非线程安全
    • 查找、插入和删除的时间复杂度平均为 O(1)
  • 适用场景:需要快速查找键值对的场景
java
import java.util.HashMap;
import java.util.Map;

public class HashMapExample {
  public static void main(String[] args) {
    Map<String, Integer> map = new HashMap<>();
    map.put("Apple", 10);
    map.put("Banana", 20);
    map.put("Cherry", 30);
    map.put("Apple", 15); // 替换旧值

    System.out.println("Map: " + map);
    System.out.println("Value of 'Apple': " + map.get("Apple")); // 输出: 15
    System.out.println("Contains key 'Banana': " + map.containsKey("Banana")); // 输出: true
    System.out.println("Size: " + map.size()); // 输出: 3
    
    // Java 8+ 新方法
    System.out.println("Value of 'Date': " + map.getOrDefault("Date", 0)); // 输出: 0
    map.putIfAbsent("Banana", 25); // 不会替换,因为键已存在
    map.putIfAbsent("Date", 40); // 会添加
    System.out.println("After putIfAbsent: " + map);
  }
}

HashMap 扩容机制

  • 初始容量为 16,默认负载因子为 0.75
  • 当元素数量 > 容量 * 负载因子时,扩容为原来的 2 倍
  • JDK 1.8 开始,当链表长度 > 8 且数组长度 >= 64 时,链表转为红黑树
  • 当红黑树节点数 < 6 时,红黑树转回链表

LinkedHashMap

  • 底层实现:基于哈希表和双向链表,继承自 HashMap
  • 特点
    • 维护键值对的插入顺序或访问顺序
    • 允许一个 null 键和多个 null 值
    • 非线程安全
    • 性能略低于 HashMap,但仍然很好
  • 适用场景:需要维护键值对顺序的场景
java
import java.util.LinkedHashMap;
import java.util.Map;

public class LinkedHashMapExample {
  public static void main(String[] args) {
    // 按插入顺序
    Map<String, Integer> map = new LinkedHashMap<>();
    map.put("Apple", 10);
    map.put("Banana", 20);
    map.put("Cherry", 30);

    System.out.println("LinkedHashMap: " + map); // 输出: {Apple=10, Banana=20, Cherry=30},保持插入顺序
    
    // 按访问顺序(可用于实现 LRU 缓存)
    Map<String, Integer> lruMap = new LinkedHashMap<>(16, 0.75f, true);
    lruMap.put("A", 1);
    lruMap.put("B", 2);
    lruMap.put("C", 3);
    
    lruMap.get("A"); // 访问 A
    System.out.println("LRU Map: " + lruMap); // 输出: {B=2, C=3, A=1},A 移到最后
  }
}

TreeMap

  • 底层实现:基于红黑树(NavigableMap 实现)
  • 特点
    • 键按自然顺序或自定义比较器排序
    • 不允许 null 键,但允许 null 值
    • 非线程安全
    • 查找、插入和删除的时间复杂度为 O(log n)
  • 适用场景:需要键有序且需要快速查找的场景
java
import java.util.Map;
import java.util.TreeMap;

public class TreeMapExample {
  public static void main(String[] args) {
    Map<String, Integer> map = new TreeMap<>();
    map.put("Banana", 20);
    map.put("Apple", 10);
    map.put("Cherry", 30);

    System.out.println("TreeMap: " + map); // 输出: {Apple=10, Banana=20, Cherry=30},按键排序
    
    // 导航方法
    TreeMap<String, Integer> treeMap = new TreeMap<>(map);
    System.out.println("First key: " + treeMap.firstKey()); // 输出: Apple
    System.out.println("Last key: " + treeMap.lastKey()); // 输出: Cherry
    System.out.println("Lower key of 'Banana': " + treeMap.lowerKey("Banana")); // 输出: Apple
    System.out.println("Higher key of 'Banana': " + treeMap.higherKey("Banana")); // 输出: Cherry
  }
}

Hashtable

  • 底层实现:基于哈希表
  • 特点
    • 线程安全(使用 synchronized 同步)
    • 不允许 null 键和 null 值
    • 性能低于 HashMap
  • 适用场景:需要线程安全的键值对存储(但通常推荐使用 ConcurrentHashMap
java
import java.util.Hashtable;
import java.util.Map;

public class HashtableExample {
  public static void main(String[] args) {
    Map<String, Integer> map = new Hashtable<>();
    map.put("Apple", 10);
    map.put("Banana", 20);
    map.put("Cherry", 30);

    System.out.println("Hashtable: " + map);
  }
}

ConcurrentHashMap

  • 底层实现:基于分段锁或 CAS 操作的并发哈希表
  • 特点
    • 线程安全,性能优于 Hashtable
    • 不允许 null 键和 null 值
    • 支持高并发访问
  • 适用场景:高并发环境下的键值对存储
java
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {
  public static void main(String[] args) {
    Map<String, Integer> map = new ConcurrentHashMap<>();
    map.put("Apple", 10);
    map.put("Banana", 20);
    map.put("Cherry", 30);

    System.out.println("ConcurrentHashMap: " + map);
    
    // 线程安全的操作
    map.computeIfAbsent("Date", k -> 40);
    System.out.println("After computeIfAbsent: " + map);
  }
}

常用操作示例

遍历 Map

java
import java.util.HashMap;
import java.util.Map;

public class MapTraversalExample {
  public static void main(String[] args) {
    Map<String, Integer> map = new HashMap<>();
    map.put("Apple", 10);
    map.put("Banana", 20);
    map.put("Cherry", 30);

    // 遍历键
    System.out.println("Keys:");
    for (String key : map.keySet()) {
      System.out.println(key);
    }

    // 遍历值
    System.out.println("\nValues:");
    for (Integer value : map.values()) {
      System.out.println(value);
    }

    // 遍历键值对(推荐)
    System.out.println("\nEntries:");
    for (Map.Entry<String, Integer> entry : map.entrySet()) {
      System.out.println(entry.getKey() + " = " + entry.getValue());
    }

    // Java 8+ forEach 方法
    System.out.println("\nUsing forEach:");
    map.forEach((key, value) -> System.out.println(key + " = " + value));
  }
}

使用自定义对象作为键

java
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

class Person {
  private String id;
  private String name;
  
  public Person(String id, String name) {
    this.id = id;
    this.name = name;
  }
  
  // 必须重写 equals() 和 hashCode() 方法
  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Person person = (Person) o;
    return Objects.equals(id, person.id);
  }
  
  @Override
  public int hashCode() {
    return Objects.hash(id);
  }
  
  @Override
  public String toString() {
    return "Person{id='" + id + "', name='" + name + "'}";
  }
}

public class CustomKeyMapExample {
  public static void main(String[] args) {
    Map<Person, String> map = new HashMap<>();
    map.put(new Person("001", "张三"), "工程师");
    map.put(new Person("002", "李四"), "设计师");
    map.put(new Person("001", "王五"), "教师"); // 替换旧值,因为 id 相同
    
    System.out.println("Map:");
    map.forEach((person, job) -> System.out.println(person + " -> " + job));
  }
}

迭代器

迭代器(Iterator)是一种设计模式,用于遍历集合中的元素。Java 集合框架中的 Collection 接口继承了 Iterable 接口,因此所有集合都可以使用迭代器遍历。

Iterator 接口

Iterator 接口提供了基本的遍历功能:

方法名功能描述
boolean hasNext()判断是否还有下一个元素
E next()返回下一个元素
default void remove()移除当前元素(可选操作)
default void forEachRemaining(Consumer<? super E> action)对剩余元素执行操作(Java 8+)

ListIterator 接口

ListIterator 继承自 Iterator,提供了双向遍历和修改功能:

方法名功能描述
boolean hasPrevious()判断是否还有前一个元素
E previous()返回前一个元素
int nextIndex()返回下一个元素的索引
int previousIndex()返回前一个元素的索引
void set(E e)替换当前元素
void add(E e)在当前位置插入元素

使用示例

java
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

public class IteratorExample {
  public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("A");
    list.add("B");
    list.add("C");
    
    // 使用 Iterator 遍历并删除元素
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
      String element = iterator.next();
      if ("B".equals(element)) {
        iterator.remove(); // 安全删除
      }
    }
    System.out.println("After remove: " + list); // 输出: [A, C]
    
    // 使用 ListIterator 遍历并修改元素
    ListIterator<String> listIterator = list.listIterator();
    while (listIterator.hasNext()) {
      String element = listIterator.next();
      listIterator.set(element.toLowerCase());
    }
    System.out.println("After set: " + list); // 输出: [a, c]
    
    // 反向遍历
    System.out.println("\nReverse:");
    while (listIterator.hasPrevious()) {
      System.out.println(listIterator.previous());
    }
  }
}

迭代器的注意事项

  1. 并发修改异常:在使用迭代器遍历集合时,如果通过集合的方法(非迭代器的 remove/add/set)修改集合,会抛出 ConcurrentModificationException
java
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ConcurrentModificationExample {
  public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("A");
    list.add("B");
    list.add("C");
    
    // 错误:在迭代过程中通过集合方法修改
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
      String element = iterator.next();
      if ("B".equals(element)) {
        // list.remove(element); // 抛出 ConcurrentModificationException
      }
    }
    
    // 正确:使用迭代器的 remove 方法
    iterator = list.iterator();
    while (iterator.hasNext()) {
      String element = iterator.next();
      if ("B".equals(element)) {
        iterator.remove(); // 安全
      }
    }
  }
}
  1. 增强 for 循环的本质:增强 for 循环(for-each)底层使用迭代器实现,因此也不能在遍历过程中通过集合方法修改集合
java
import java.util.ArrayList;
import java.util.List;

public class ForEachLimitationExample {
  public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("A");
    list.add("B");
    list.add("C");
    
    // 错误:会抛出 ConcurrentModificationException
    for (String element : list) {
      if ("B".equals(element)) {
        // list.remove(element); // 错误
      }
    }
    
    // 正确:使用迭代器或 removeIf(Java 8+)
    list.removeIf(element -> "B".equals(element));
    System.out.println("After removeIf: " + list); // 输出: [A, C]
  }
}

集合的线程安全问题

非线程安全的集合

大多数集合类(如 ArrayListHashMapHashSet)都是非线程安全的。在多线程环境下使用这些集合可能导致:

  • 数据不一致
  • 索引越界异常
  • 死循环(如 HashMap 在并发扩容时)
  • ConcurrentModificationException

线程安全的解决方案

1. 使用同步包装器

Collections 工具类提供了同步包装器方法:

java
import java.util.*;

public class SynchronizedCollectionExample {
  public static void main(String[] args) {
    // 同步的 List
    List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());
    
    // 同步的 Set
    Set<String> synchronizedSet = Collections.synchronizedSet(new HashSet<>());
    
    // 同步的 Map
    Map<String, Integer> synchronizedMap = Collections.synchronizedMap(new HashMap<>());
    
    // 注意:迭代时需要手动同步
    synchronized (synchronizedList) {
      for (String element : synchronizedList) {
        System.out.println(element);
      }
    }
  }
}

2. 使用并发集合(推荐)

java.util.concurrent 包提供了高性能的并发集合:

java
import java.util.*;
import java.util.concurrent.*;

public class ConcurrentCollectionExample {
  public static void main(String[] args) {
    // 并发的 List
    List<String> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
    
    // 并发的 Set
    Set<String> copyOnWriteArraySet = new CopyOnWriteArraySet<>();
    
    // 并发的 Map
    Map<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();
    
    // 并发的 Queue
    Queue<String> concurrentLinkedQueue = new ConcurrentLinkedQueue<>();
    
    // 阻塞队列
    BlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(10);
    BlockingQueue<String> linkedBlockingQueue = new LinkedBlockingQueue<>();
  }
}

3. 使用 synchronized 关键字

java
import java.util.*;

public class SynchronizedBlockExample {
  private List<String> list = new ArrayList<>();
  
  public void add(String element) {
    synchronized (list) {
      list.add(element);
    }
  }
  
  public String get(int index) {
    synchronized (list) {
      return list.get(index);
    }
  }
}

并发集合性能对比

集合类型读性能写性能适用场景
Hashtable不推荐使用
Collections.synchronizedXxx需要完全同步的场景
ConcurrentHashMap高并发读写场景(推荐)
CopyOnWriteArrayList读多写少的场景

Arrays 工具类

Arrays 类提供了操作数组的静态方法。

常用方法

方法名功能描述
static <T> List<T> asList(T... a)将数组转为 List(固定大小)
static void sort(int[] a)对数组排序
static int binarySearch(int[] a, int key)二分查找(数组必须有序)
static void fill(int[] a, int val)填充数组
static int[] copyOf(int[] original, int newLength)复制数组
static String toString(int[] a)数组转字符串
static boolean equals(int[] a, int[] a2)比较数组是否相等

使用示例

java
import java.util.Arrays;
import java.util.List;

public class ArraysExample {
  public static void main(String[] args) {
    // asList:数组转 List
    String[] array = {"A", "B", "C"};
    List<String> list = Arrays.asList(array);
    System.out.println("List: " + list);
    
    // 注意:asList 返回的 List 大小固定,不能添加或删除元素
    // list.add("D"); // 抛出 UnsupportedOperationException
    
    // 如果需要可变 List,需要用 new ArrayList<>(Arrays.asList(array))
    
    // sort:排序
    int[] numbers = {3, 1, 4, 1, 5, 9};
    Arrays.sort(numbers);
    System.out.println("Sorted: " + Arrays.toString(numbers));
    
    // binarySearch:二分查找
    int index = Arrays.binarySearch(numbers, 4);
    System.out.println("Index of 4: " + index);
    
    // fill:填充
    int[] filled = new int[5];
    Arrays.fill(filled, 10);
    System.out.println("Filled: " + Arrays.toString(filled));
    
    // copyOf:复制
    int[] copied = Arrays.copyOf(numbers, 3);
    System.out.println("Copied: " + Arrays.toString(copied));
    
    // equals:比较
    int[] a = {1, 2, 3};
    int[] b = {1, 2, 3};
    System.out.println("Equals: " + Arrays.equals(a, b)); // 输出: true
  }
}

Collections 工具类

Collections 类提供了操作集合的静态方法,包括排序、搜索、同步等。

java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CollectionsExample {
  public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
    list.add(3);
    list.add(1);
    list.add(4);
    list.add(2);
    
    System.out.println("Original list: " + list);
    
    // 排序
    Collections.sort(list);
    System.out.println("Sorted list: " + list);
    
    // 反转
    Collections.reverse(list);
    System.out.println("Reversed list: " + list);
    
    // 洗牌
    Collections.shuffle(list);
    System.out.println("Shuffled list: " + list);
    
    // 查找最大值和最小值
    System.out.println("Max: " + Collections.max(list));
    System.out.println("Min: " + Collections.min(list));
    
    // 查找元素索引(列表必须有序)
    Collections.sort(list);
    System.out.println("Binary search: " + Collections.binarySearch(list, 3));
    
    // 替换所有元素
    Collections.replaceAll(list, 3, 10);
    System.out.println("After replaceAll: " + list);
    
    // 填充
    Collections.fill(list, 0);
    System.out.println("After fill: " + list);
    
    // 创建不可修改的集合
    List<Integer> unmodifiableList = Collections.unmodifiableList(new ArrayList<>(list));
    // unmodifiableList.add(5); // 抛出 UnsupportedOperationException
    
    // 创建同步的集合
    List<Integer> synchronizedList = Collections.synchronizedList(list);
    
    // 创建单元素集合
    List<Integer> singletonList = Collections.singletonList(1);
    System.out.println("Singleton list: " + singletonList);
    
    // 创建空集合
    List<Integer> emptyList = Collections.emptyList();
    System.out.println("Empty list: " + emptyList);
  }
}

泛型与集合

泛型提供了编译时类型安全,允许在集合中存储特定类型的对象。

java
import java.util.ArrayList;
import java.util.List;

public class GenericCollectionExample {
  public static void main(String[] args) {
    // 使用泛型指定集合中存储的元素类型
    List<String> stringList = new ArrayList<>();
    stringList.add("Hello");
    stringList.add("World");
    // stringList.add(123); // 编译错误,不能添加非 String 类型的元素
    
    // 泛型方法
    List<Integer> intList = createList(1, 2, 3);
    System.out.println("Integer list: " + intList);
  }
  
  // 泛型方法
  @SafeVarargs
  public static <T> List<T> createList(T... items) {
    List<T> list = new ArrayList<>();
    for (T item : items) {
      list.add(item);
    }
    return list;
  }
}

Java 8+ 集合新特性

Java 8 引入了许多集合操作的新特性,特别是 Stream API。

java
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Java8CollectionFeatures {
  public static void main(String[] args) {
    List<String> list = Arrays.asList("Apple", "Banana", "Cherry", "Date");
    
    // 过滤和转换
    List<String> filtered = list.stream()
      .filter(s -> s.length() > 5)
      .map(String::toUpperCase)
      .collect(Collectors.toList());
    
    System.out.println("Filtered list: " + filtered);
    
    // 分组
    Map<Integer, List<String>> groupedByLength = list.stream()
      .collect(Collectors.groupingBy(String::length));
    
    System.out.println("Grouped by length: " + groupedByLength);
    
    // 求和、平均值等
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    int sum = numbers.stream().mapToInt(Integer::intValue).sum();
    double average = numbers.stream().mapToInt(Integer::intValue).average().orElse(0);
    
    System.out.println("Sum: " + sum);
    System.out.println("Average: " + average);
    
    // 并行流
    int parallelSum = numbers.parallelStream().mapToInt(Integer::intValue).sum();
    System.out.println("Parallel sum: " + parallelSum);
  }
}

集合选择指南

如何选择合适的集合类型

场景推荐集合类型原因
需要存储有序元素,允许重复ArrayList随机访问效率高
频繁插入和删除元素LinkedList插入和删除效率高
需要存储不重复元素,不关心顺序HashSet查找效率高
需要存储不重复元素,保持插入顺序LinkedHashSet维护插入顺序
需要存储不重复元素,元素有序TreeSet元素自动排序
需要存储键值对,不关心顺序HashMap查找效率高
需要存储键值对,保持插入顺序LinkedHashMap维护插入顺序
需要存储键值对,键有序TreeMap键自动排序
需要队列功能LinkedListArrayDeque效率高
需要优先级队列PriorityQueue按优先级排序
需要栈功能ArrayDeque效率高于 Stack
多线程环境,需要线程安全的 ListCopyOnWriteArrayList读多写少性能好
多线程环境,需要线程安全的 MapConcurrentHashMap高并发性能好

性能对比

集合类型查找插入删除空间占用
ArrayListO(1)O(n)O(n)
LinkedListO(n)O(1)O(1)高(存储指针)
HashSetO(1)O(1)O(1)
TreeSetO(log n)O(log n)O(log n)高(红黑树)
HashMapO(1)O(1)O(1)
TreeMapO(log n)O(log n)O(log n)高(红黑树)
PriorityQueueO(n)O(log n)O(log n)

性能考虑

  1. 时间复杂度

    • ArrayListHashSetHashMap:大多数操作为 O(1)
    • LinkedList:插入和删除为 O(1),访问为 O(n)
    • TreeSetTreeMap:大多数操作为 O(log n)
  2. 空间复杂度

    • ArrayList:空间利用率高
    • LinkedList:每个元素需要额外空间存储指针
    • HashSetHashMap:需要额外空间存储哈希表
    • TreeSetTreeMap:需要额外空间存储红黑树
  3. 内存占用

    • 基本类型:考虑使用专门的基本类型集合(如 Trove 库)
    • 大对象:考虑使用弱引用或软引用

常见误区

1. Arrays.asList() 返回的 List 不能添加或删除元素

java
import java.util.Arrays;
import java.util.List;

public class AsListPitfall {
  public static void main(String[] args) {
    String[] array = {"A", "B", "C"};
    List<String> list = Arrays.asList(array);
    
    // list.add("D"); // 抛出 UnsupportedOperationException
    // list.remove(0); // 抛出 UnsupportedOperationException
    
    // 正确做法:创建新的 ArrayList
    List<String> mutableList = new ArrayList<>(Arrays.asList(array));
    mutableList.add("D"); // 正常
    System.out.println(mutableList);
  }
}

2. ArrayList 构造时指定容量

java
import java.util.ArrayList;
import java.util.List;

public class ArrayListCapacityExample {
  public static void main(String[] args) {
    // 如果知道元素数量,建议指定初始容量,避免多次扩容
    List<String> list = new ArrayList<>(1000); // 初始容量 1000
    
    // 添加 1000 个元素不会触发扩容
    for (int i = 0; i < 1000; i++) {
      list.add("Element" + i);
    }
  }
}

3. subList() 返回的是视图,不是副本

java
import java.util.ArrayList;
import java.util.List;

public class SubListPitfall {
  public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
      list.add(i);
    }
    
    List<Integer> subList = list.subList(0, 5);
    
    // 修改 subList 会影响原列表
    subList.set(0, 100);
    System.out.println("Original list: " + list); // 原列表也被修改
    
    // 删除 subList 的元素也会影响原列表
    subList.clear();
    System.out.println("After clear: " + list); // 原列表的前 5 个元素被删除
    
    // 注意:如果对原列表进行结构性修改(添加/删除),subList 的操作会抛出异常
  }
}

4. 自定义对象作为 HashMapHashSet 的键时必须重写 equals()hashCode()

java
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

class Student {
  private String id;
  private String name;
  
  public Student(String id, String name) {
    this.id = id;
    this.name = name;
  }
  
  // 必须重写 equals() 和 hashCode()
  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Student student = (Student) o;
    return Objects.equals(id, student.id);
  }
  
  @Override
  public int hashCode() {
    return Objects.hash(id);
  }
}

public class CustomKeyPitfall {
  public static void main(String[] args) {
    Map<Student, Integer> map = new HashMap<>();
    Student s1 = new Student("001", "张三");
    Student s2 = new Student("001", "张三");
    
    map.put(s1, 100);
    System.out.println("Value: " + map.get(s2)); // 如果不重写 equals 和 hashCode,输出 null
  }
}

5. 使用 == 比较字符串

java
import java.util.HashMap;
import java.util.Map;

public class StringComparisonPitfall {
  public static void main(String[] args) {
    Map<String, Integer> map = new HashMap<>();
    map.put("key", 1);
    
    String key = new String("key");
    System.out.println(map.get(key)); // 输出: 1,因为 HashMap 使用 equals 比较
    
    // 错误:使用 == 比较字符串
    String a = "hello";
    String b = new String("hello");
    System.out.println(a == b); // 输出: false
    System.out.println(a.equals(b)); // 输出: true
  }
}

实战案例

案例 1:统计单词频率

java
import java.util.HashMap;
import java.util.Map;

public class WordFrequencyExample {
  public static void main(String[] args) {
    String text = "hello world hello java world";
    
    // 统计单词频率
    Map<String, Integer> frequency = new HashMap<>();
    for (String word : text.split(" ")) {
      frequency.merge(word, 1, Integer::sum); // Java 8+ 方法
    }
    
    System.out.println("Word frequency: " + frequency);
    // 输出: {hello=2, world=2, java=1}
    
    // 找出出现次数最多的单词
    frequency.entrySet().stream()
      .max(Map.Entry.comparingByValue())
      .ifPresent(entry -> System.out.println("Most frequent: " + entry.getKey()));
  }
}

案例 2:使用 LinkedHashMap 实现 LRU 缓存

java
import java.util.LinkedHashMap;
import java.util.Map;

class LRUCache<K, V> extends LinkedHashMap<K, V> {
  private final int capacity;
  
  public LRUCache(int capacity) {
    super(capacity, 0.75f, true); // accessOrder = true
    this.capacity = capacity;
  }
  
  @Override
  protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
    return size() > capacity;
  }
}

public class LRUCacheExample {
  public static void main(String[] args) {
    LRUCache<String, Integer> cache = new LRUCache<>(3);
    
    cache.put("A", 1);
    cache.put("B", 2);
    cache.put("C", 3);
    System.out.println("Initial: " + cache);
    
    cache.get("A"); // 访问 A
    cache.put("D", 4); // 添加 D,B 被淘汰
    System.out.println("After adding D: " + cache); // 输出: {C=3, A=1, D=4}
  }
}

案例 3:使用 PriorityQueue 实现任务调度

java
import java.util.PriorityQueue;
import java.util.Queue;

class Task implements Comparable<Task> {
  private String name;
  private int priority;
  
  public Task(String name, int priority) {
    this.name = name;
    this.priority = priority;
  }
  
  @Override
  public int compareTo(Task other) {
    return Integer.compare(this.priority, other.priority); // 优先级小的先执行
  }
  
  @Override
  public String toString() {
    return "Task{name='" + name + "', priority=" + priority + "}";
  }
}

public class TaskSchedulerExample {
  public static void main(String[] args) {
    Queue<Task> taskQueue = new PriorityQueue<>();
    
    taskQueue.offer(new Task("Low priority task", 3));
    taskQueue.offer(new Task("High priority task", 1));
    taskQueue.offer(new Task("Medium priority task", 2));
    
    // 按优先级执行任务
    while (!taskQueue.isEmpty()) {
      System.out.println("Executing: " + taskQueue.poll());
    }
    // 输出顺序: High priority task -> Medium priority task -> Low priority task
  }
}

案例 4:使用 ConcurrentHashMap 实现线程安全的计数器

java
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

class Counter {
  private ConcurrentMap<String, Integer> counters = new ConcurrentHashMap<>();
  
  public void increment(String key) {
    counters.merge(key, 1, Integer::sum);
  }
  
  public int get(String key) {
    return counters.getOrDefault(key, 0);
  }
  
  public void printAll() {
    counters.forEach((k, v) -> System.out.println(k + ": " + v));
  }
}

public class CounterExample {
  public static void main(String[] args) throws InterruptedException {
    Counter counter = new Counter();
    
    // 模拟多线程环境
    Runnable task = () -> {
      for (int i = 0; i < 1000; i++) {
        counter.increment("key");
      }
    };
    
    Thread[] threads = new Thread[10];
    for (int i = 0; i < 10; i++) {
      threads[i] = new Thread(task);
      threads[i].start();
    }
    
    for (Thread thread : threads) {
      thread.join();
    }
    
    counter.printAll(); // 输出: key: 10000
  }
}

面试要点

1. ArrayListLinkedList 的区别

特性ArrayListLinkedList
底层实现动态数组双向链表
随机访问O(1)O(n)
插入删除O(n)(需要移动元素)O(1)(只需修改指针)
内存占用较低较高(存储指针)
缓存友好性
适用场景频繁查询、随机访问频繁插入、删除

2. HashMap 的底层实现原理

JDK 1.8 中 HashMap 的底层实现:

  1. 数据结构:数组 + 链表 + 红黑树
  2. 哈希计算hash(key) ^ (hash(key) >>> 16)
  3. 索引计算(n - 1) & hash
  4. 扩容机制:当元素数量 > 容量 * 负载因子时,扩容为原来的 2 倍
  5. 链表转红黑树:当链表长度 > 8 且数组长度 >= 64 时
  6. 红黑树转链表:当红黑树节点数 < 6 时

3. ConcurrentHashMap 的实现原理

JDK 1.7:

  • 使用分段锁(Segment)
  • 每个 Segment 继承 ReentrantLock

JDK 1.8:

  • 使用 CAS + synchronized
  • 锁粒度更细(锁单个链表头节点)
  • 性能更好

4. HashSet 如何保证元素不重复

  1. 调用元素的 hashCode() 方法计算哈希值
  2. 如果哈希值不同,直接添加
  3. 如果哈希值相同,调用 equals() 方法比较
  4. 如果 equals() 返回 true,则认为是重复元素,不添加

5. 为什么重写 equals() 必须重写 hashCode()

因为如果两个对象 equals() 返回 true,它们的 hashCode() 必须相同。如果不重写 hashCode(),可能导致两个相等的对象哈希值不同,从而在 HashSetHashMap 中出现重复。

6. fail-fastfail-safe 的区别

  • fail-fast:迭代器在遍历时直接访问集合内容,如果在遍历过程中集合结构发生变化(非迭代器自身方法),会抛出 ConcurrentModificationException。如 ArrayListHashMap 的迭代器。
  • fail-safe:迭代器在遍历时访问集合的副本,因此在遍历过程中集合结构的变化不会影响迭代器。如 CopyOnWriteArrayListConcurrentHashMap 的迭代器。

7. 如何让 ArrayList 线程安全

  1. 使用 Collections.synchronizedList()
  2. 使用 CopyOnWriteArrayList
  3. 使用 synchronized 关键字手动同步

8. TreeMapHashMap 的区别

特性HashMapTreeMap
底层实现哈希表红黑树
元素顺序无序有序(按键排序)
查找性能O(1)O(log n)
null 键允许一个不允许
适用场景需要快速查找需要键有序

9. PriorityQueue 的实现原理

  • 底层使用数组实现的小顶堆(默认)或大顶堆
  • 插入元素时,从底部向上调整堆(上浮)
  • 删除元素时,从顶部向下调整堆(下沉)
  • 不允许 null 元素
  • 非线程安全,线程安全版本为 PriorityBlockingQueue

10. 如何选择合适的集合

根据需求选择:

  • 需要快速查找HashMapHashSet
  • 需要有序TreeMapTreeSetLinkedHashMapLinkedHashSet
  • 需要队列功能LinkedListArrayDequePriorityQueue
  • 需要线程安全ConcurrentHashMapCopyOnWriteArrayList
  • 需要频繁插入删除LinkedList
  • 需要频繁随机访问ArrayList

总结

Java 集合框架提供了丰富的数据结构,用于存储和操作对象集合。选择合适的集合类型对于编写高效、可维护的代码至关重要。

关键要点

  1. List 集合

    • 有序集合,允许重复元素
    • ArrayList 适合随机访问,LinkedList 适合频繁插入和删除
    • CopyOnWriteArrayList 适合读多写少的并发场景
  2. Queue 队列

    • 先进先出(FIFO)的数据结构
    • LinkedList 可作为队列使用,ArrayDeque 性能更好
    • PriorityQueue 按优先级排序
  3. Set 集合

    • 不包含重复元素的集合
    • HashSet 提供快速查找,LinkedHashSet 维护插入顺序,TreeSet 维护排序顺序
    • 自定义对象作为元素时,必须重写 equals()hashCode() 方法
  4. Map 集合

    • 存储键值对的集合
    • HashMap 提供快速查找,LinkedHashMap 维护插入顺序,TreeMap 维护排序顺序
    • 自定义对象作为键时,必须重写 equals()hashCode() 方法
  5. 迭代器

    • 提供遍历集合的标准方式
    • 使用迭代器的 remove() 方法安全删除元素
    • 增强 for 循环底层使用迭代器
  6. 线程安全

    • 使用并发集合(如 ConcurrentHashMap)或同步包装器
    • 根据读写比例选择合适的线程安全集合

最佳实践

  1. 选择合适的集合类型:根据具体需求选择最合适的集合实现
  2. 使用接口编程:尽量使用接口类型(如 ListSetMap)声明变量,而不是具体实现类
  3. 重写必要的方法:自定义对象作为集合元素或键时,正确重写 equals()hashCode() 方法
  4. 考虑线程安全:多线程环境下,选择线程安全的集合实现或使用同步机制
  5. 利用泛型:使用泛型提高代码的类型安全性和可读性
  6. 使用 Stream API:在 Java 8+ 环境中,充分利用 Stream API 简化集合操作
  7. 指定初始容量:如果知道元素数量,建议在创建集合时指定初始容量,避免多次扩容
  8. 注意迭代器的并发修改异常:在迭代过程中不要通过集合方法修改集合

通过掌握这些集合类的特性和使用方法,可以编写出更加高效、灵活和可维护的 Java 代码。

版本差异(旧版 → Java 21)

特性旧版(Java 8)Java 9/10/16/21
不可变集合工厂Collections.unmodifiableXxx 包装可变集合List.of/Set.of/Map.of(Java 9)、List.copyOf(Java 10)真正不可变
迭代删除手动 Iterator.remove()Collection.removeIf(Java 8)
空集合Collections.emptyList()List.of() 等工厂方法(Java 9)
顺序遍历forEach(Java 8)Stream.toList()(Java 16)不可变收集
序列化安全普通集合record 天然不可变(Java 16+),集合中承载 record 更安全

继续阅读