Java集合框架之ArrayList解析

目录

一、ArrayList概述

二、优缺点分析

三、底层数据结构

四、源码分析ArrayList初始化容量

五、源码分析ArrayList扩容策略

六、ArrayList集合源码分析

1. 属性分析

2. 构造方法分析

无参构造方法

指定初始容量的构造方法

传入集合的构造方法

3. 添加元素

add(E e) 方法

add(int index, E element) 方法

4. 修改元素

set(int index, E element) 方法

5. 插入元素

6. 删除元素

remove(int index) 方法

remove(Object o) 方法

七、Vector

1. 底层结构与线程安全

2. 初始容量与扩容策略

3. 为何逐渐被弃用?


一、ArrayList概述

ArrayList 是Java集合框架中最常用的类之一,基于动态数组实现,继承自 AbstractList 并实现了 List 接口。它提供了高效的元素访问能力,适合频繁查询少随机增删的场景。本文将从源码层面深入分析其设计原理、性能特点及最佳实践。

二、优缺点分析

优点:

高效随机访问:通过下标直接访问元素,底层是数组,因此根据下标查找元素的时间复杂度是O(1)。因此检索效率高。

list.get(3); // 直接访问索引3处的元素

尾部操作高效:在数组末尾添加或删除元素时,时间复杂度为 O(1)(无需移动元素)。

缺点:

随机增删效率低:在数组中间插入或删除元素时,需移动后续元素,时间复杂度为 O(n)。不过只要数组的容量还没满,对末尾元素进行增删,效率不受影响

list.add(2, "Java"); // 插入到索引2,后续元素后移

内存浪费:数组容量可能大于实际元素数量,占用额外内存

三、底层数据结构

ArrayList 的核心是一个 Object[] elementData 数组,所有元素按插入顺序依次存储。数组的连续内存特性使得其支持通过下标快速访问元素(时间复杂度 O(1))。

// JDK 11源码片段
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    transient Object[] elementData; // 存储元素的数组
    private int size;              // 实际元素个数
}

四、源码分析ArrayList初始化容量

在 Java 中,ArrayList 类有多个构造方法,不同构造方法对初始容量的处理不同:

  • 无参构造方法:初始容量为 0,当第一次调用 add 方法时,容量会被初始化为 10。
  • 带初始容量参数的构造方法:使用指定的初始容量。
  • 带集合参数的构造方法:初始容量为传入集合的大小。

下面是 ArrayList 无参构造方法的源码:

/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

其中 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是一个空数组:

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

无参构造:创建一个空数组(初始容量为0),首次调用 add() 方法时扩容至 10

List<String> list = new ArrayList<>(); // elementData = {}
list.add("A"); // 首次扩容,容量变为10

当第一次调用 add 方法时,会触发扩容逻辑,将容量初始化为 10,相关源码如下:

private int newCapacity(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity <= 0) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return minCapacity;
    }
    return (newCapacity - MAX_ARRAY_SIZE <= 0)
        ? newCapacity
        : hugeCapacity(minCapacity);
}

其中 DEFAULT_CAPACITY 的值为 10:

private static final int DEFAULT_CAPACITY = 10;

五、源码分析ArrayList扩容策略

  • 触发条件:当元素数量 size 超过数组长度 elementData.length

  • 扩容规则:新容量为原容量的 1.5倍(通过位运算优化:newCapacity = oldCapacity + (oldCapacity >> 1))。

  • 扩容实现:调用 Arrays.copyOf() 创建新数组并拷贝原数据。

// JDK 11源码中的grow方法
private Object[] grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = ArraysSupport.newLength(oldCapacity,
            minCapacity - oldCapacity, // 最小增量
            oldCapacity >> 1);         // 扩容增量(原容量的一半)
    return elementData = Arrays.copyOf(elementData, newCapacity);
}

六、ArrayList集合源码分析

1. 属性分析

ArrayList 类的主要属性如下:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    // 序列化版本号
    private static final long serialVersionUID = 8683452581122892189L;

    // 默认初始容量
    private static final int DEFAULT_CAPACITY = 10;

    // 用于空实例的共享空数组实例
    private static final Object[] EMPTY_ELEMENTDATA = {};

    // 用于默认大小的空实例的共享空数组实例。
    // 我们将其与 EMPTY_ELEMENTDATA 区分开来,以了解添加第一个元素时要膨胀多少。
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    // 存储 ArrayList 元素的数组缓冲区。
    // ArrayList 的容量是这个数组缓冲区的长度。
    transient Object[] elementData; // non-private to simplify nested class access

    // ArrayList 的大小(包含的元素个数)
    private int size;

    // 数组的最大容量
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}
  • DEFAULT_CAPACITY:默认初始容量,值为 10。
  • EMPTY_ELEMENTDATA 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA:两个空数组,用于不同的初始化情况。
  • elementData:存储 ArrayList 元素的数组。
  • sizeArrayList 中实际包含的元素个数。
  • MAX_ARRAY_SIZE:数组的最大容量,为 Integer.MAX_VALUE - 8

2. 构造方法分析

ArrayList 有三个主要的构造方法:

无参构造方法

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

使用无参构造方法创建 ArrayList 时,elementData 被初始化为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,初始容量为 0,当第一次添加元素时,容量会被初始化为 10。

指定初始容量的构造方法

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

如果指定的初始容量大于 0,则创建一个指定大小的数组;如果初始容量为 0,则使用 EMPTY_ELEMENTDATA;如果初始容量小于 0,则抛出 IllegalArgumentException 异常。

传入集合的构造方法

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

将传入集合转换为数组,并将其赋值给 elementData。如果集合不为空,则检查数组类型是否为 Object[],如果不是则进行复制转换;如果集合为空,则使用 EMPTY_ELEMENTDATA

建议:在使用 ArrayList 时,最好预测大概数量并给定初始化容量,这样可以减少扩容次数,提高性能。

3. 添加元素

add(E e) 方法
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}
  • 首先调用 ensureCapacityInternal 方法确保数组有足够的容量。
  • 如果是第一次添加元素且使用的是无参构造方法创建的 ArrayList,则将容量初始化为 10。
  • 如果当前容量不足,则调用 grow 方法进行扩容,新容量为原容量的 1.5 倍。
  • 最后将元素添加到数组的末尾,并将 size 加 1。
add(int index, E element) 方法
public void add(int index, E element) {
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
  • 首先检查插入位置是否合法。
  • 然后确保数组有足够的容量。
  • 使用 System.arraycopy 方法将插入位置之后的元素向后移动一位。
  • 将元素插入到指定位置,并将 size 加 1。

4. 修改元素

set(int index, E element) 方法
public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

@SuppressWarnings("unchecked")
E elementData(int index) {
    return (E) elementData[index];
}
  • 首先检查索引是否合法。
  • 获取指定位置的旧元素。
  • 将新元素赋值给指定位置。
  • 返回旧元素

5. 插入元素

插入元素的逻辑与 add(int index, E element) 方法类似,都是在指定位置插入元素,并将后续元素向后移动。

6. 删除元素

remove(int index) 方法
public E remove(int index) {
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}
  • 首先检查索引是否合法。
  • 获取指定位置的元素。
  • 计算需要移动的元素个数。
  • 使用 System.arraycopy 方法将后续元素向前移动一位。
  • 将最后一个元素置为 null,以便垃圾回收。
  • 返回被删除的元素。
remove(Object o) 方法
public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
}
遍历数组,找到第一个与指定对象相等的元素。
调用 fastRemove 方法删除该元素。
如果找到并删除元素,则返回 true;否则返回 false。

七、Vector

1. 底层结构与线程安全

  • 数组实现:与 ArrayList 一样,Vector 底层通过数组存储数据,支持动态扩容。
  • 同步方法:Vector 的几乎所有方法(如 addremoveget)都使用 synchronized 关键字修饰,保证多线程环境下操作的安全性(线程排队执行)。
  • 性能代价:同步机制导致线程争抢锁资源,高并发场景下效率显著低于 ArrayList

2. 初始容量与扩容策略

  • 默认容量:创建 Vector 时,若未指定大小,初始容量为 10
  • 扩容规则
    • 当元素数量超过当前容量时,Vector 默认扩容为原容量的 2 倍(例如:10 → 20 → 40)。
    • 可通过构造函数参数 capacityIncrement 自定义增量(若设为正数,则每次增加固定值;若为0,则直接翻倍)。

3. 为何逐渐被弃用?

  • 同步效率低:粗暴的全方法同步在高并发场景下成为性能瓶颈。
  • 更优替代方案
    • ArrayList + 同步控制:使用 Collections.synchronizedList(new ArrayList<>()) 包装非线程安全集合,按需同步。
    • 并发容器:如 CopyOnWriteArrayList(写时复制,读无锁)或 ConcurrentLinkedQueue(CAS无锁算法),针对并发场景优化。
    • 显式锁机制:通过 ReentrantLock 或 synchronized 块精细化控制同步范围。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值