ArrayList 源码阅读(从 JDK1.8 源码角度理解扩容机制)

文章目录

    • 一、ArrayList 简单介绍
    • 二、ArrayList 方法源码阅读
      • 2.1 ArrayList() 方法
      • 2.2 add() 方法
      • 2.3 get() 方法
      • 2.3 set() 方法
      • 2.3 remove() 方法
    • 三、总结

一、ArrayList 简单介绍

我们都知道:ArrayList一个有序的、元素可重复的集合。那么 ArrayList 的底层到底是通过什么来存储元素的呢 ?

通过查看 ArrayList 的源码,我们可以看到在 ArrayList 的定义中有这样一个成员变量 —— elementData

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
     
    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access
  
    // ......
}

通过注释,我们也不难看出,这个 Object 数组就是 ArrayList 底层用于存储元素的那个数组。

也就是说,ArrayList 的底层其实是一个 Object 数组在存储数据。这也就解释了,为什么 ArrayList 是一个有序的、元素可以重复的集合,因为这本质上就是数组的特点。

二、ArrayList 方法源码阅读

2.1 ArrayList() 方法

通常,我们使用 ArrayList 集合时,都会直接 new 一个无参的构造方法:

ArrayList<Integer> list = new ArrayList<>();

那么这段代码发生了什么呢 ?

首先来看无参构造方法 ArrayList()

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

从注释可以得知:调用无参构造方法 ArrayList() 会创建一个初始容量为 10 的空 List 集合。

事实真的是这样吗?再来看看这个 DEFAULTCAPACITY_EMPTY_ELEMENTDATA

/**
 * Shared empty array instance used for default sized empty instances. We
 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 * first element is added.
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
     };

很显然,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是一个空数组,那么为什么前面说会创建一个初始容量为 10 的 List 集合 ?

这个疑问,需要由 add() 方法来解答。

2.2 add() 方法

当我们往集合中直接添加一个元素时,调用的是下面的这个方法:

/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return {@code true} (as specified by {@link Collection#add})
 */
public boolean add(E e) {
     
    modCount++;	// 记录操作 ArrayList 集合的次数
    add(e, elementData, size);
    return true;
}

add() 方法中,会调用另一个需要三个参数的 add() 方法来执行添加元素的过程,其方法体如下:

/**
 * This helper method split out from add(E) to keep method
 * bytecode size under 35 (the -XX:MaxInlineSize default value),
 * which helps when add(E) is called in a C1-compiled loop.
 */
private void add(E e, Object[] elementData, int s) {
     
    if (s == elementData.length)	// 判断 elementData 数组中当前元素的个数是否等于 elementData 数组的容量
        elementData = grow();	// 如果相等,则调用 grow() 方法,进行扩容
    elementData[s] = e;	// 给对应位置的索引赋值
    size = s + 1;	// elementData 已存储元素个数 +1
}

在上面的这个方法中,有一个很重要的方法 ,就是 grow(),它负责数组的扩容:

private Object[] grow() {
     
    return grow(size + 1);	// 返回 grow(size+1) 的结果
}

在这个 grow() 方法中,实际调用了一个有参的 grow() 方法:

/**
 * Increases the capacity to ensure that it can hold at least the
 * number of elements specified by the minimum capacity argument.
 *
 * @param minCapacity the desired minimum capacity
 * @throws OutOfMemoryError if minCapacity is less than zero
 */
private Object[] grow(int minCapacity) {
     
    return elementData = Arrays.copyOf(elementData,
                                       newCapacity(minCapacity));
}

在上面的这个方法中,我们可以看出来:ArrayList 所谓的扩容,其实就是将原来的数组中的内容,复制到一个新的更大的数组中

这个方法中,又调用了 newCapacity() 方法,这个方法返回的就是扩增后的容量:

/**
 * Returns a capacity at least as large as the given minimum capacity.
 * Returns the current capacity increased by 50% if that suffices.
 * Will not return a capacity greater than MAX_ARRAY_SIZE unless
 * the given minimum capacity is greater than MAX_ARRAY_SIZE.
 *
 * @param minCapacity the desired minimum capacity
 * @throws OutOfMemoryError if minCapacity is less than zero
 */
private int newCapacity(int minCapacity) {
     	// minCapacity 代表的是:数组可再插入一个元素的最小容量
    // overflow-conscious code
    int oldCapacity = elementData.length;	// 首先获取当前 elementData 数组的存储容量
    int newCapacity = oldCapacity + (oldCapacity >> 1);	// 将存储容量扩容 1.5 倍
    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)	// 扩容后容量和 MAX_ARRAY_SIZE 比较
        ? newCapacity	// 如果小于 MAX_ARRAY_SIZE,则容量为扩容后容量
        : hugeCapacity(minCapacity);	// 如果大于 MAX_ARRAY_SIZE,则执行 hugeCapacity 方法
}

对于上面的方法,我们首先明确一个参数 minCapacity,这个参数代表的是最小容量需求(比如 ArrayList 中元素个数为 9 个,如果要新增一个元素,那么 ArrayList 的容量至少为 10 才能满足需求,那么这个 minCapacity 就是 10)。

我们还可以看出,一旦进入 newCapacity() 这个方法,数组的容量就会扩为原来的 1.5 倍。如果扩容后的容量仍然小于等于 minCapacityelementData 是一个空数组,那么将会以 DEFAULT_CAPACITYminCapacity 的最大值作为容量。

DEFAULT_CAPACITY 定义为:

/**
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;

这里就解开了调用无参构造方法 ArrayList() 时,集合就会被赋予初始容量 10 的这个疑问。

也就是说:当调用无参构造方法 ArrayList() 时,集合并未被赋予初始容量,只有第一次调用 add() 方法时,才会给集合赋予初始容量 10。之后,如果集合中的元素达到了容量上限时,再新增元素,集合的容量将扩容为原来的 1.5 倍。

那么集合的容量会无限的扩容吗?

当然不会!我们再看看上面的 newCapacity() 方法中有如下几行代码:

// private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private int newCapacity(int minCapacity) {
     	// minCapacity 代表的是:数组可再插入一个元素的最小容量
    // overflow-conscious code
	/* ...... */
    
    return (newCapacity - MAX_ARRAY_SIZE <= 0)	// 扩容后容量和 MAX_ARRAY_SIZE 比较, 
        ? newCapacity	// 如果小于等于 MAX_ARRAY_SIZE,则容量为扩容后容量
        : hugeCapacity(minCapacity);	// 如果大于 MAX_ARRAY_SIZE,则执行 hugeCapacity 方法
}

当扩容后的新容量 newCapacity 小于 MAX_ARRAY_SIZE 时,会 newCapacity 作为新的容量返回,否则以 hugeCapacity 方法的返回值作为新的容量返回。

hugeCapacity() 方法定义如下:

// private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private static int hugeCapacity(int minCapacity) {
     
    if (minCapacity < 0) // overflow	// 如果最小容量需求小于0,则抛出内存溢出
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE)	// 最小容量需求和 MAX_ARRAY_SIZE 比较
        ? Integer.MAX_VALUE	// 如果大于 MAX_ARRAY_SIZE,则以 Integer.MAX_VALUE 作为新容量
        : MAX_ARRAY_SIZE;	// 否则,返回 MAX_ARRAY_SIZE 作为新容量
}

至此,关于 add() 方法的主要流程,我们已经全部摸清。

弄清了 add() 方法,我们也就明白了 ArrayList 的扩容机制。

总结下来 ArrayList 的扩容机制就是:当调用无参构造方法 ArrayList() 时,集合并未被赋予初始容量,只有第一次调用 add() 方法时,才会给集合赋予初始容量 10。之后,如果集合中的元素达到了容量上限时,再新增元素,集合的容量将扩容为原来的 1.5 倍。但是 ArrayList 的容量并不会无限扩容,如果扩容后的新容量大于 MAX_ARRAY_SIZE ,最大的新容量将只能扩为 Integer.MAX_VALUE,如果再超出容量,将会抛出内存溢出错误

2.3 get() 方法

get() 方法的作用是获取对应下标位置的元素。它的实现也非常简单:

/**
 * Returns the element at the specified position in this list.
 *
 * @param  index index of the element to return
 * @return the element at the specified position in this list
 */
public E get(int index) {
     
    // 1. 首先检查 index 是否越界
    Objects.checkIndex(index, size);
    // 2. 返回下标 index 对应的元素
    return elementData(index);
}

2.3 set() 方法

set() 方法的作用就是更新对应下标位置的元素。它的实现同样非常简单:

/**
 * Replaces the element at the specified position in this list with
 * the specified element.
 *
 * @param index index of the element to replace
 * @param element element to be stored at the specified position
 * @return the element previously at the specified position
 */
public E set(int index, E element) {
     
    // 1. 检查 index 是否越界
    Objects.checkIndex(index, size);
    // 2. 获取 index 位置的旧元素
    E oldValue = elementData(index);
    // 3. 新元素覆盖旧元素
    elementData[index] = element;
    // 4. 返回旧元素
    return oldValue;
}

2.3 remove() 方法

关于 remove() 方法,有 remove(Object o)remove(int index),此处讲解的是 remove(int index) 方法。

remove() 方法的作用就是从 List 集合中移除指定下标的元素。它的实现如下:

/**
 * Removes the element at the specified position in this list.
 * Shifts any subsequent elements to the left (subtracts one from their
 * indices).
 *
 * @param index the index of the element to be removed
 * @return the element that was removed from the list
 */
public E remove(int index) {
     
    // 1. 检查 index 是否越界
    Objects.checkIndex(index, size);
    final Object[] es = elementData;

    // 2. 获取旧值
    @SuppressWarnings("unchecked") E oldValue = (E) es[index];
    // 3. 执行删除
    fastRemove(es, index);
	// 4. 返回删除的值
    return oldValue;
}

remove() 方法中,实际执行删除操作的是 fastRemove() 方法:

/**
 * Private remove method that skips bounds checking and does not
 * return the value removed.
 */
private void fastRemove(Object[] es, int i) {
     
    // 1. 操作次数 +1
    modCount++;
    final int newSize;
    // 2. 要删除元素,给 size-1,得到 newSize。判断 newSize 是否大于要删除元素索引
    if ((newSize = size - 1) > i)
        // 3. 如果 i 有效,则拷贝 es 的 i+1 之后的元素到 es 的 i 之后
        // 也就是相当于 es 数组 i 下标之后的元素整体左移一位
        System.arraycopy(es, i + 1, es, i, newSize - i);
    // 4. 由于 es 数组 i 下标之后的元素左移了一位,因此将 es 数组的最后一位置空
    es[size = newSize] = null;
}

从源码我们可以看出来,ArrayList 集合删除指定下标位置元素的思路就是:将待删除元素之后的元素全部向前移动一位拷贝到原数组中,最后将原数组最后一位置空,即可完成元素的删除操作

remove(Object o) 方法的核心思路其实和 remove(Object[] es, int i) 完全一致:

/**
 * Removes the first occurrence of the specified element from this list,
 * if it is present.  If the list does not contain the element, it is
 * unchanged.  More formally, removes the element with the lowest index
 * {@code i} such that
 * {@code Objects.equals(o, get(i))}
 * (if such an element exists).  Returns {@code true} if this list
 * contained the specified element (or equivalently, if this list
 * changed as a result of the call).
 *
 * @param o element to be removed from this list, if present
 * @return {@code true} if this list contained the specified element
 */
public boolean remove(Object o) {
     
    final Object[] es = elementData;
    final int size = this.size;
    int i = 0;
    // 1. 找到指定元素第一次出现的下标
    found: {
     
        if (o == null) {
     
            for (; i < size; i++)
                if (es[i] == null)
                    break found;
        } else {
     
            for (; i < size; i++)
                if (o.equals(es[i]))
                    break found;
        }
        return false;
    }
    // 2. 执行删除动作
    fastRemove(es, i);
    return true;
}

其思想就是:首先找到待删除元素第一次出现的下标,然后调用 fastaRemove() 方法最终执行删除动作。

三、总结

从阅读 ArrayList 集合的源码的整个过程下来,我发现ArrayList 类最复杂的代码其实就是 add() 方法部分。而 add() 方法中最复杂的代码就是 ArrayList() 的扩容问题。至于 get()set()remove() 方法,本质上都是对数组的基本操作,很容易理解。

你可能感兴趣的:(java,java,arraylist,arraylist,扩容机制,arraylist扩容机制,源码阅读)