目录

一、集合框架的整体架构

1. Collection体系

2. Map体系

二、List接口:ArrayList vs LinkedList

1. ArrayList —— 基于动态数组

2. LinkedList —— 基于双向链表

3. 选型建议

三、ArrayList扩容机制(面试高频考点)

扩容触发条件

扩容流程详解

四、迭代器模式(Iterator Pattern)

Iterator接口

迭代器的使用

for-each的实现原理

五、fail-fast机制

什么是fail-fast

fail-fast的实现原理

正确的删除方式

六、其他常用集合简介

HashSet

ArrayDeque

七、总结


Java集合框架(Java Collections Framework)是Java编程中最基础、最常用的知识体系之一。无论是日常开发还是面试考察,集合框架都占据着举足轻重的地位。本文将从整体架构出发,深入剖析Collection与Map两大体系的核心设计思想。

一、集合框架的整体架构

Java集合框架主要由两大接口体系构成:CollectionMap

1. Collection体系

Collection 是所有单列集合的根接口,它有三个重要子接口:

  • List:有序、可重复的集合
  • Set:不可重复的集合
  • Queue:队列,用于处理排队场景
// List - 有序可重复
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
list.add("Java"); // 允许重复

// Set - 无序不可重复
Set<String> set = new HashSet<>();
set.add("Java");
set.add("Python");
set.add("Java"); // 不会添加成功

// Queue - 队列
Queue<String> queue = new LinkedList<>();
queue.offer("Java");
queue.offer("Python");
String first = queue.poll(); // "Java"

2. Map体系

Map 是键值对映射的根接口,每个key最多映射一个value。

// Map - 键值对
Map<String, Integer> map = new HashMap<>();
map.put("Java", 1);
map.put("Python", 2);
int value = map.get("Java"); // 1

Collection和Map的根本区别在于:Collection存储单个元素,Map存储键值对。这个设计思想贯穿了整个集合框架。

二、List接口:ArrayList vs LinkedList

List接口最常见的两个实现类是ArrayListLinkedList,它们的底层数据结构截然不同,适用场景也完全不同。

1. ArrayList —— 基于动态数组

ArrayList底层使用Object[]数组实现,它是最常用的List实现类。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    // 默认初始容量
    private static final int DEFAULT_CAPACITY = 10;
    // 存储元素的数组
    transient Object[] elementData;
    // 实际元素数量
    private int size;
}

核心特性:

  • 支持随机访问,通过下标访问元素的时间复杂度为O(1)
  • 尾部添加元素的平均时间复杂度为O(1)
  • 中间插入或删除元素需要移动后续元素,时间复杂度为O(n)
  • 线程不安全

2. LinkedList —— 基于双向链表

LinkedList底层使用双向链表实现,同时实现了ListDeque接口。

public class LinkedList<E> extends AbstractSequentialList<E>
        implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
    transient int size = 0;
    transient Node<E> first;
    transient Node<E> last;

    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;
    }
}

核心特性:

  • 不支持随机访问,查找元素需要遍历,时间复杂度为O(n)
  • 头部和尾部的增删操作时间复杂度为O(1)
  • 中间插入或删除需要先定位到节点,时间复杂度为O(n)
  • 额外内存开销较大(每个节点需要存储前后指针)
  • 线程不安全

3. 选型建议

// 场景1:频繁随机访问,使用ArrayList
List<String> names = new ArrayList<>();
names.get(100); // O(1) 高效

// 场景2:频繁在头部增删,使用LinkedList
LinkedList<String> queue = new LinkedList<>();
queue.addFirst("first"); // O(1)
queue.removeFirst();      // O(1)

// 场景3:已知大致容量,预设容量避免频繁扩容
List<String> bigList = new ArrayList<>(10000);

实际开发中的经验法则: 绝大多数场景下,ArrayList都是更好的选择。现代CPU的缓存机制对连续内存(数组)更加友好,即使在中间插入删除场景中,ArrayList的实际性能也经常优于LinkedList

三、ArrayList扩容机制(面试高频考点)

ArrayList的扩容机制是面试中非常高频的考点,必须深入理解。

扩容触发条件

当向ArrayList中添加元素时,如果当前数组容量不足,就会触发扩容。

// 添加元素的核心逻辑
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 确保容量足够
    elementData[size++] = e;
    return true;
}

扩容流程详解

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    // 新容量 = 旧容量 * 1.5(位运算比乘法效率更高)
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 拷贝原数组到新数组
    elementData = Arrays.copyOf(elementData, newCapacity);
}

关键点:

  1. 扩容倍数是1.5倍(不是2倍),通过 oldCapacity + (oldCapacity >> 1) 实现
  2. 底层使用 Arrays.copyOf(),本质是调用 System.arraycopy() 进行内存拷贝
  3. 如果使用无参构造函数创建ArrayList,初始容量为0(JDK8优化),第一次add时扩容为10
  4. 扩容操作涉及数组拷贝,性能开销较大,因此建议在已知容量时使用有参构造函数
// 性能对比
// 不预设容量 - 可能多次扩容
List<Integer> list1 = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
    list1.add(i); // 可能触发多次扩容
}

// 预设容量 - 零扩容
List<Integer> list2 = new ArrayList<>(100000);
for (int i = 0; i < 100000; i++) {
    list2.add(i); // 不会触发扩容
}

四、迭代器模式(Iterator Pattern)

迭代器模式是集合框架的核心设计模式之一,它提供了一种统一的方式来遍历集合元素,而无需暴露集合的内部结构。

Iterator接口

public interface Iterator<E> {
    boolean hasNext();  // 是否还有下一个元素
    E next();           // 返回下一个元素
    void remove();      // 删除当前元素(可选操作)
}

迭代器的使用

List<String> list = new ArrayList<>(Arrays.asList("Java", "Python", "Go"));

// 方式1:for-each(本质是语法糖,底层也是迭代器)
for (String item : list) {
    System.out.println(item);
}

// 方式2:显式使用迭代器
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String item = it.next();
    System.out.println(item);
}

for-each的实现原理

for-each语法糖在编译后会被转换为迭代器模式:

// 编译前
for (String item : list) {
    System.out.println(item);
}

// 编译后(等价代码)
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String item = it.next();
    System.out.println(item);
}

五、fail-fast机制

fail-fast(快速失败)是Java集合框架中一个非常重要的错误检测机制。

什么是fail-fast

当多个线程在遍历集合时对集合进行结构性修改(添加、删除元素),或者在单线程遍历过程中通过非迭代器方式修改集合,迭代器会立即抛出ConcurrentModificationException

List<String> list = new ArrayList<>(Arrays.asList("Java", "Python", "Go"));

// 错误示范:在for-each中删除元素
try {
    for (String item : list) {
        if ("Python".equals(item)) {
            list.remove(item); // 抛出ConcurrentModificationException
        }
    }
} catch (ConcurrentModificationException e) {
    System.out.println("触发了fail-fast机制!");
}

fail-fast的实现原理

ArrayList内部维护一个modCount(修改计数器),每次对集合进行结构性修改时,modCount会自增。迭代器在创建时会记录当前的modCount值为expectedModCount,每次调用next()时都会检查这两个值是否相等。

private class Itr implements Iterator<E> {
    int expectedModCount = modCount;

    public E next() {
        checkForComodification(); // 检查是否被修改
        // ... 返回元素逻辑
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

正确的删除方式

// 方式1:使用迭代器的remove方法
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if ("Python".equals(it.next())) {
        it.remove(); // 正确:会同步更新expectedModCount
    }
}

// 方式2:使用removeIf(JDK8+)
list.removeIf(item -> "Python".equals(item));

// 方式3:反向遍历时使用List的remove
for (int i = list.size() - 1; i >= 0; i--) {
    if ("Python".equals(list.get(i))) {
        list.remove(i);
    }
}

六、其他常用集合简介

HashSet

基于HashMap实现,元素作为key存储,value统一使用一个PRESENT对象。

// HashSet源码
public class HashSet<E> extends AbstractSet<E> implements Set<E> {
    private transient HashMap<E,Object> map;
    private static final Object PRESENT = new Object();

    public boolean add(E e) {
        return map.put(e, PRESENT) == null;
    }
}

ArrayDeque

基于循环数组实现的双端队列,性能优于StackLinkedList

// 推荐用ArrayDeque替代Stack
Deque<String> stack = new ArrayDeque<>();
stack.push("first");
stack.push("second");
String top = stack.pop(); // "second"

七、总结

特性 ArrayList LinkedList
底层结构 动态数组 双向链表
随机访问 O(1) O(n)
头部增删 O(n) O(1)
尾部增删 O(1)均摊 O(1)
内存占用 紧凑 较大(指针开销)
线程安全

集合框架是Java编程的基石,理解其设计思想和实现原理,不仅能帮助我们写出更高效的代码,更是面试中展现技术深度的关键所在。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐