java-arraylist 源码分析 1

深入分析 Java 中的 `ArrayList` 源码

`ArrayList` 是 Java 集合框架中的一个重要类,它基于数组实现,提供了动态数组的功能。`ArrayList` 是一个非常常用的集合类,因为它在随机访问和遍历方面性能优越。本文将详细分析 `ArrayList` 的源码,包括其数据结构、构造方法、核心操作、自动扩容机制等。

1. `ArrayList` 的基本数据结构

`ArrayList` 是一个动态数组,它的底层是一个 `Object` 类型的数组。在 `ArrayList` 的实现中,主要使用以下几个关键字段:

```java

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;

// 空数组实例,当初始容量为0时使用

private static final Object[] EMPTY_ELEMENTDATA = {};

// 默认空数组实例,当使用默认构造函数时使用

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 存储元素的数组

transient Object[] elementData;

// 元素数量

private int size;

}

```

  • `elementData`:这是实际存储元素的数组。

  • `size`:这是当前 `ArrayList` 中包含的元素数量。

  • `DEFAULT_CAPACITY`:这是默认的初始容量。

  • `EMPTY_ELEMENTDATA` 和 `DEFAULTCAPACITY_EMPTY_ELEMENTDATA`:分别是用于初始化时的空数组。

2. 构造方法

`ArrayList` 提供了多个构造方法:

2.1 默认构造方法

```java

public ArrayList() {

this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

}

```

默认构造方法将 `elementData` 初始化为 `DEFAULTCAPACITY_EMPTY_ELEMENTDATA`,即一个空数组。当第一次添加元素时,数组将扩展到默认初始容量。

2.2 指定初始容量的构造方法

```java

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);

}

}

```

此构造方法允许用户指定 `ArrayList` 的初始容量。如果初始容量大于 0,则创建一个相应大小的数组;如果初始容量为 0,则使用空数组;如果初始容量为负数,则抛出 `IllegalArgumentException`。

2.3 从另一个集合创建 `ArrayList`

```java

public ArrayList(Collection<? extends E> c) {

elementData = c.toArray();

if ((size = elementData.length) != 0) {

if (elementData.getClass() != Object[].class)

elementData = Arrays.copyOf(elementData, size, Object[].class);

} else {

this.elementData = EMPTY_ELEMENTDATA;

}

}

```

此构造方法从另一个集合创建 `ArrayList`,并将集合中的所有元素添加到 `ArrayList` 中。

3. 核心操作方法

3.1 添加元素

添加元素的方法主要有 `add(E e)` 和 `add(int index, E element)`:

```java

public boolean add(E e) {

ensureCapacityInternal(size + 1); // 扩容检查

elementData[size++] = e;

return true;

}

public void add(int index, E element) {

rangeCheckForAdd(index);

ensureCapacityInternal(size + 1); // 扩容检查

System.arraycopy(elementData, index, elementData, index + 1, size - index);

elementData[index] = element;

size++;

}

```

  • `add(E e)`:在数组末尾添加元素,需要先检查容量是否足够,如果不足,则进行扩容。

  • `add(int index, E element)`:在指定位置插入元素,需要先检查索引的有效性,然后将指定位置后的元素向后移动,再插入新元素。

3.2 确保容量

`ensureCapacityInternal(int minCapacity)` 方法用于确保数组有足够的容量,如果不足则进行扩容:

```java

private void ensureCapacityInternal(int minCapacity) {

ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));

}

private void ensureExplicitCapacity(int minCapacity) {

modCount++;

// overflow-conscious code

if (minCapacity - elementData.length > 0)

grow(minCapacity);

}

private static int calculateCapacity(Object[] elementData, int minCapacity) {

if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {

return Math.max(DEFAULT_CAPACITY, minCapacity);

}

return minCapacity;

}

```

4. 自动扩容机制

当 `ArrayList` 中的元素超过当前数组的容量时,需要进行扩容。`grow(int minCapacity)` 方法用于扩容:

```java

private void grow(int minCapacity) {

int oldCapacity = elementData.length;

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);

}

private static int hugeCapacity(int minCapacity) {

if (minCapacity < 0) // overflow

throw new OutOfMemoryError();

return (minCapacity > MAX_ARRAY_SIZE) ?

Integer.MAX_VALUE :

MAX_ARRAY_SIZE;

}

```

  • `oldCapacity`:旧容量,即扩容前数组的长度。

  • `newCapacity`:新容量,为旧容量的 1.5 倍。

  • 如果 `newCapacity` 仍小于所需的最小容量,则将其设为 `minCapacity`。

  • 如果 `newCapacity` 超过了 `MAX_ARRAY_SIZE`,则调用 `hugeCapacity(int minCapacity)` 方法进行处理。

  • 最后通过 `Arrays.copyOf` 方法将旧数组复制到新数组中。

5. 删除元素

`ArrayList` 提供了删除元素的方法 `remove(int index)` 和 `remove(Object o)`:

```java

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;

}

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

}

```

  • `remove(int index)`:根据索引删除元素,首先检查索引的有效性,然后通过 `System.arraycopy` 方法将索引后的元素向前移动,覆盖要删除的元素。

  • `remove(Object o)`:根据对象删除元素,通过遍历找到目标元素并调用 `fastRemove(int index)` 方法进行删除。

6. 获取元素和修改元素

`ArrayList` 提供了获取元素的方法 `get(int index)` 和修改元素的方法 `set(int index, E element)`:

```java

public E get(int index) {

rangeCheck(index);

return elementData(index);

}

public E set(int index, E element) {

rangeCheck(index);

E oldValue = elementData(index);

elementData[index] = element;

return oldValue;

}

```

  • `get(int index)`:根据索引获取元素,首先检查索引的有效性,然后返回数组中的对应元素。

  • `set(int index, E element)`:根据索引修改元素,首先检查索引的有效性,然后将指定位置的元素替换为新元素,并返回旧元素。

相关推荐
TangKenny9 分钟前
计算网络信号
java·算法·华为
景鹤9 分钟前
【算法】递归+深搜:814.二叉树剪枝
算法
肘击鸣的百k路10 分钟前
Java 代理模式详解
java·开发语言·代理模式
iiFrankie13 分钟前
SCNU习题 总结与复习
算法
城南vision16 分钟前
Docker学习—Docker核心概念总结
java·学习·docker
wyh要好好学习24 分钟前
SpringMVC快速上手
java·spring
尢词26 分钟前
SpringMVC
java·spring·java-ee·tomcat·maven
Mr. zhihao32 分钟前
享元模式在 JDK 中的应用解析
java·享元模式
茶馆大橘36 分钟前
微服务系列五:避免雪崩问题的限流、隔离、熔断措施
java·jmeter·spring cloud·微服务·云原生·架构·sentinel
wrx繁星点点36 分钟前
享元模式:高效管理共享对象的设计模式
java·开发语言·spring·设计模式·maven·intellij-idea·享元模式