java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java  ArrayList集合

Java 的ArrayList集合底层实现与最佳实践

作者:李少兄

本文主要介绍了Java的ArrayList集合类的核心概念、底层实现、关键成员变量、初始化机制、容量演变、扩容机制、性能分析、核心方法源码解析、特殊场景与常见问题、性能优化与最佳实践以及与LinkedList的对比,感兴趣的朋友跟随小编一起看看吧

1. 核心概念与底层实现

1.1 ArrayList 的本质

ArrayList 是基于 动态数组List 实现类,其底层数据结构是 Object[] elementData。它通过动态扩容机制(自动扩展数组长度)实现灵活的存储需求,但牺牲了线程安全性。

1.1.1 底层数据结构

ArrayList的底层基于对象数组Object[] elementData)实现:

JDK 1.7 vs JDK 1.8的初始化差异

特性JDK 1.7JDK 1.8+
无参构造的elementData.length100(DEFAULTCAPACITY_EMPTY_ELEMENTDATA
首次添加元素时的扩容不触发(已预分配)触发扩容到10
内存占用立即分配10个元素的内存空间延迟分配,节省初始内存

1.1.2 动态扩容机制

扩容触发条件

当调用add()方法时,若当前数组容量(elementData.length)小于size + 1,则触发扩容。

扩容规则

1.2 关键成员变量

// 核心成员变量
transient Object[] elementData; // 底层数组,存储元素
private int size; // 当前元素个数
// 静态常量
private static final Object[] EMPTY_ELEMENTDATA = new Object[0]; // 空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0]; // 无参构造时使用
private static final int DEFAULT_CAPACITY = 10; // 默认初始容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // 最大数组长度

2. 初始化机制:从0到10的蜕变

2.1 无参构造的陷阱

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; // 初始化为空数组(长度0)
}

2.2 首次扩容的触发过程

当调用 add() 方法时:

public boolean add(E e) {
    ensureCapacityInternal(size + 1); // 此处触发扩容
    elementData[size++] = e;
    return true;
}

2.2.1ensureCapacityInternal的核心逻辑

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 强制设置为10
    }
    ensureExplicitCapacity(minCapacity);
}

2.2.2ensureExplicitCapacity的逻辑

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // 如果当前容量不足
    if (minCapacity - elementData.length > 0) {
        grow(minCapacity);
    }
}

2.2.3grow方法的扩容计算

private void grow(int minCapacity) {
    int oldCapacity = elementData.length; // 当前容量
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量 = 原容量的1.5倍
    if (newCapacity - minCapacity < 0) {
        newCapacity = minCapacity; // 若新容量仍不足,则直接使用minCapacity
    }
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
        newCapacity = hugeCapacity(minCapacity); // 处理溢出
    }
    elementData = Arrays.copyOf(elementData, newCapacity); // 复制数据到新数组
}

3. 容量演变的详细过程

初始容量的定义

关键成员变量

// 源码片段(JDK 1.8+)
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    // 默认初始容量为10
    private static final int DEFAULT_CAPACITY = 10;
    // 空数组,用于区分不同状态的空ArrayList
    private static final Object[] EMPTY_ELEMENTDATA = {};
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 无参构造时使用
    // 底层数组,存储元素
    transient Object[] elementData;
    // 当前元素个数
    private int size;
}

无参构造方法的初始化

// 无参构造方法(JDK 1.8+)
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; // 初始化为空数组
}

首次添加元素时的初始化

当调用add()方法添加第一个元素时,会触发以下流程:

// add(E e)方法(JDK 1.8+)
public boolean add(E e) {
    // 确保容量足够
    ensureCapacityInternal(size + 1);
    elementData[size++] = e;
    return true;
}
private void ensureCapacityInternal(int minCapacity) {
    // 如果当前elementData是DEFAULTCAPACITY_EMPTY_ELEMENTDATA(即无参构造的情况)
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // 将minCapacity设为max(DEFAULT_CAPACITY, minCapacity)
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // 如果当前容量不足
    if (minCapacity - elementData.length > 0) 
        grow(minCapacity);
}

初始容量的动态变化

3.1 不同场景的容量变化

3.1.1 无参构造的初始状态

ArrayList<String> list = new ArrayList<>();
System.out.println(list.size()); // 0
System.out.println(list.elementData.length); // 0(JDK 1.8+)

3.1.2 首次添加元素

list.add("Hello");
// 此时:
System.out.println(list.size()); // 1
System.out.println(list.elementData.length); // 10(扩容到10)

3.1.3 添加第11个元素

for (int i = 1; i < 10; i++) {
    list.add("World");
}
list.add("World"); // 第11个元素
// 此时:
System.out.println(list.size()); // 11
System.out.println(list.elementData.length); // 15(10 → 15)

3.1.4 添加第16个元素

for (int i = 0; i < 5; i++) {
    list.add("Java");
}
// 此时:
System.out.println(list.elementData.length); // 22(15 → 22)

4. 扩容的数学模型与性能分析

4.1 扩容的数学公式

4.2 扩容的渐进特性

当前容量新容量计算新容量实际值
00 + 010(强制修正)
1010 + 515
1515 + 722
2222 + 1133

4.3 扩容的性能代价

5. 核心方法的源码解析

5.1 尾部添加add(E e)

public boolean add(E e) {
    ensureCapacityInternal(size + 1); // 确保容量足够
    elementData[size++] = e; // 直接写入数组末尾
    return true;
}

5.2 中间插入add(int index, E element)

public void add(int index, E element) {
    rangeCheckForAdd(index); // 索引检查
    ensureCapacityInternal(size + 1);
    // 将[index, size) 的元素后移一位
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    elementData[index] = element;
    size++;
}

5.3 删除元素remove(int index)

public E remove(int index) {
    rangeCheck(index); // 索引检查
    modCount++;
    E oldValue = (E) elementData[index];
    int numMoved = size - index - 1;
    if (numMoved > 0) {
        System.arraycopy(elementData, index + 1, elementData, index, numMoved);
    }
    elementData[--size] = null; // 释放引用
    return oldValue;
}

6. 特殊场景与常见问题

6.1 初始容量为0的误解

6.2 扩容溢出的处理

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) { // 无法处理负数
        throw new OutOfMemoryError();
    }
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

6.3 并发修改异常(ConcurrentModificationException)

List<String> list = new ArrayList<>();
// 错误示例:
for (String s : list) {
    if (s.equals("remove")) {
        list.remove(s); // 抛出异常
    }
}
// 正确示例:
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    if (s.equals("remove")) {
        it.remove(); // 安全删除
    }
}

7. 性能优化与最佳实践

7.1 预分配容量

// 优化示例:已知数据量时预分配容量
ArrayList<String> list = new ArrayList<>(1000000); // 初始容量100万
for (int i = 0; i < 1000000; i++) {
    list.add("Data" + i);
}

7.2 避免频繁扩容

7.3 使用trimToSize()

// JDK 11+:回收冗余容量
list.trimToSize(); // 将数组长度调整为当前size

8. 与 LinkedList 的深度对比

8.1 底层结构对比

特性ArrayListLinkedList
存储结构连续内存数组双向链表(每个节点存储前后指针)
访问速度快(O(1))慢(O(n)需遍历)
插入/删除速度慢(中间操作需移动元素,O(n))快(修改指针,O(1))
内存占用低(连续内存)高(每个节点存储额外指针)

8.2 适用场景

9. 源码级优化技巧

9.1 避免toArray()的性能陷阱

// 错误示例:频繁调用toArray()导致额外开销
for (Object obj : list.toArray()) {
    // ...
}
// 优化示例:直接使用elementData(需谨慎)
Object[] arr = list.toArray();
for (Object obj : arr) {
    // ...
}

9.2 使用subList()的注意事项

// 避免直接修改子列表的引用
List<String> sublist = list.subList(0, 10);
sublist.clear(); // 会修改原列表

10. 总结:ArrayList 的设计哲学

到此这篇关于Java 的ArrayList集合的文章就介绍到这了,更多相关Java ArrayList集合内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文