ArrayList源码阅读

本节目标:阅读面试中常问的 ArrayList 八股对应的源码,了解其背后的代码逻辑

注意,本次阅读的源码是 JDK 1.8 版本

ArrayList 常问八股

ArrayList 是 List 的动态数组实现,它本身并不是线程安全的,因此性能相比于线程安全的 list 实现如 vector,copyWriteArrayList 要好很多。

ArrayList 中可以存储任何类型的对象,包括 null

其中我们常将 arraylist 和 LinkedList 进行对比,分别从以下几个方面来看

  • 底层数据结构,arraylist 是使用的数组实现可以快速访问,而 LinkedList 是使用的双向链表,通过节点间的指针进行元素访问。
  • 插入和删除的操作效率不同,arraylist 由于底层数据结构是数组,因此如果是在尾部插入或者删除,效率为 O (1), 其余位置皆为 O (n)。而 linkedList 由于底层数据结构为双向链表,因此对于首尾的插入删除效率为 O (1),其余位置皆为 O (n)
  • 随机位置的访问效率,arraylist 由于是数组,因此随机访问的效率为 O (1), 而 linkedlist 由于是链表,随机访问为 O (n)
  • 空间占用,arraylist 由于底层是数组,因此在创建时需要分配一段连续的内存空间,因此会占用较大的空间;而 linkedlist 每个元素只需要分配指针和元素,相对而言较小一些
  • 适用场景,arraylist 适用于读较多的场景,linkedlist 相比而言适用于频繁的插入删除操作
  • 线程安全,两者都不是线程安全的

ArrayList 的扩容机制

当 arraylist 在添加元素时,若当前元素数量已经达到了内部数组的上限,就会触发扩容机制,其扩容过程如下

  • 计算新的容量,一般会扩大为 2 原容量的 1.5 倍,然后检查是否超过了最大容量限制
  • 创建新数组,根据新的容量创建一个新的数组
  • 将原数组元素复制到新数组中去
  • 更新 arraylist 的数组引用
  • 完成扩容

以上就是面试的 Arraylist 较为常见的八股,接下来我们就来阅读对应的源码,来理解其中的代码逻辑

源码阅读

首先针对第一个常问八股,ArrayList 的底层数据结构,我们来看下对应的构造函数和属性变量。

其源代码如下

Java 复制代码
public class ArrayList<E> extends AbstractList<E>  
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable  
{  
    private static final long serialVersionUID = 8683452581122892189L;  
  
    /**  
     * Default initial capacity.     */    
     private static final int DEFAULT_CAPACITY = 10;  
  
    /**  
     * Shared empty array instance used for empty instances.     */    
     private static final Object[] 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 = {};  
  
    /**  
     * 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  
  
    /**  
     * The size of the ArrayList (the number of elements it contains).     *     * @serial  
     */  
    private int size;  
  
    /**  
     * Constructs an empty list with the specified initial capacity.     *     * @param  initialCapacity  the initial capacity of the list  
     * @throws IllegalArgumentException if the specified initial capacity  
     *         is negative     */    
    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);  
        }  
    }  
  
    /**  
     * Constructs an empty list with an initial capacity of ten.     */    
    public ArrayList() {  
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;  
    }  
  
    /**  
     * Constructs a list containing the elements of the specified     * collection, in the order they are returned by the collection's     * iterator.     *     * @param c the collection whose elements are to be placed into this list  
     * @throws NullPointerException if the specified collection is null  
     */    
     public ArrayList(Collection<? extends E> c) {  
        Object[] a = c.toArray();  
        if ((size = a.length) != 0) {  
            if (c.getClass() == ArrayList.class) {  
                elementData = a;  
            } else {  
                elementData = Arrays.copyOf(a, size, Object[].class);  
            }  
        } else {  
            // replace with empty array.  
            elementData = EMPTY_ELEMENTDATA;  
        }  
    }
...................................................
}
  • 首先注意看 ArrayList 继承自AbstractList,实现了List< E >, RandomAccess, Cloneable, java.io.Serializable 等多个接口,这些依次表明 ArrayList 是一个 List,可以进行随机访问,可以进行深/浅拷贝,可以序列化。
  • 接着我们看其中定义了默认容量 DEFAULT_CAPACITY, 空数组 EMPTY_ELEMENTDATA, 默认容量空数组 DEFAULTCAPACITY_EMPTY_ELEMENTDATA, 这两个数组都是初始化了的。
  • 然后有一个未初始化 的数组 elementData,以及一个 size 变量

然后 ArrayList 提供了三种构造函数,分别是

Java 复制代码
/**  
 * Constructs an empty list with the specified initial capacity. * * @param  initialCapacity  the initial capacity of the list  
 * @throws IllegalArgumentException if the specified initial capacity  
 *         is negative */
 
 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);  
    }  
}  
  
/**  
 * Constructs an empty list with an initial capacity of ten. */
public ArrayList() {  
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;  
}  
  
/**  
 * Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * * @param c the collection whose elements are to be placed into this list  
 * @throws NullPointerException if the specified collection is null  
 */
 public ArrayList(Collection<? extends E> c) {  
    Object[] a = c.toArray();  
    if ((size = a.length) != 0) {  
        if (c.getClass() == ArrayList.class) {  
            elementData = a;  
        } else {  
            elementData = Arrays.copyOf(a, size, Object[].class);  
        }  
    } else {  
        // replace with empty array.  
        elementData = EMPTY_ELEMENTDATA;  
    }  
}
  • 当构造函数传入初始化变量 initialCapacity
    • 若其>0, 则未初始化的数组变量 elementData 进行数组初始化,其大小就为 initialCapacity
    • 若其== 0,则令未初始化的数组变量 elementData 指向已经初始化且 size 为 0 的 EMPTY_ELEMENTDATA
    • 若其<0, 则抛出异常
  • 当构造函数为传入任何变量时
    • 则令未初始化的数组变量 elementData 指向已经初始化且 size 为 0 的 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
  • 当构造函数传入的是指定的 collection 元素的列表时
    • 令属性 size 的值等于列表的长度
    • 当 size!=0
      • 若传入 collection 的类别是 ArrayList, 则令 elementData 指向 collection 对应元素地址
      • 若类型不等,则创建一个新数组将 collection 的元素复制过去,并令 elementData 指向新数组
    • 当 size== 0 时
      • 则令 elementData 指向初始化且 size 为 0 的 EMPTY_ELEMENTDATA

以上就是 ArrayList 的初始化过程

接下来我们就来看 ArrayList 的扩容过程,这个就得和 add 元素相关联了,其源码如下

Java 复制代码
/**  
 * Appends the specified element to the end of this list. * * @param e element to be appended to this list  
 * @return <tt>true</tt> (as specified by {@link Collection#add})  
 */
 public boolean add(E e) {  
    ensureCapacityInternal(size + 1);  // Increments modCount!!  
    elementData[size++] = e;  
    return true;  
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {  
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {  
        return Math.max(DEFAULT_CAPACITY, minCapacity);  
    }  
    return minCapacity;  
}  
  
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);  
}

/**  
 * The maximum size of array to allocate. * Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in * OutOfMemoryError: Requested array size exceeds VM limit */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**  
 * 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  
 */
 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);  
}

private static int hugeCapacity(int minCapacity) {  
    if (minCapacity < 0) // overflow  
        throw new OutOfMemoryError();  
    return (minCapacity > MAX_ARRAY_SIZE) ?  
        Integer.MAX_VALUE :  
        MAX_ARRAY_SIZE;  
}
  • 通过上述代码我们可以看到 add 函数在将新元素放入 elementData 数组之前,先要调用 ensureCapacityInternal 函数确保数组的容量足够,传入的参数为 size+1,也就是要想添加新元素,当前元素集合至少需要多大的容量
  • ensureCapacityInternal 内部再调用 ensureExplicitCapacity 函数,而 ensureExplicitCapacity 调用之前,先要调用 calculateCapacity 函数计算容量
  • calculateCapacity 函数
    • 首先会判断当前数组是否为空,即也就是说判定当前是否在添加第一个元素,
      • 若是,则返回传入的传入的 minCapacityDEFAULT_CAPACITY 的最大值,DEFAULT_CAPACITY 默认为 10,这里也就表明 ArrayList 的初始容量大小为 10.
    • 否则,直接返回 minCapacity 最为当前数组的大小
  • 接着来看 ensureExplicitCapacity 函数
    • 首先 modCount ++, 这个数值表示数组修改的次数
    • 然后判定当前需要的容量大小与数组容量的差值
      • 若数组容量<需要的最小容量,调用 grow 函数进行扩容
      • 否则,元素组保持不变
  • 最后我们来看扩容函数 grow
    • 首先会根据当前数组容量大小进行计算一个新的待定容量值 newCapacity = oldCapacity + (oldCapacity >> 1), 新值为数组容量的 1.5 倍,这行代码可以看出
    • 接着判定这个待定新值 newCapacity 和需要的容量 minCapacity 进行大小比较
      • 若小于,则新值 newCapacity = minCapacity;
    • 接着再判定这个新值是否超过了 ArrayList 的默认数组大小上限 MAX_ARRAY_SIZE, 即 Integer.MAX_VALUE - 8
      • 若是,则调用 hugeCapacity 函数,令 newCapacity=MAX_ARRAY_SIZE
    • 最后 Arrays.copyOf, 创建一个新数组,大小为 newCapacity, 并将元素组元素复制过去,让 elementData 指向这个新的数组。
      以上就是 Arraylist 的完整扩容过程。

参考资料

相关推荐
lang201509282 分钟前
彻底理解CountDownLatch
java·开发语言
he___H16 分钟前
关于Amazon S3; Status Code: 403; Error Code: 403 Forbidden问题
java·报错·minio
山沐与山19 分钟前
【MQ】MQ消息队列幂等性设计与踩坑实战
java·开发语言·数据库·rocketmq
xiaowu08034 分钟前
C# 多返回值写法
java·前端·c#
银迢迢37 分钟前
springboot的拦截器配置不拦截路径没有生效
java·后端·spring
阿里巴巴P8资深技术专家1 小时前
2025 年度总结:在坚持与突破中前行
java·vue.js·人工智能·年终总结·2025
独自破碎E1 小时前
Spring Bean一共有几种作用域?
java·后端·spring
Chloeis Syntax1 小时前
MySQL初阶学习日记(8)--- JDBC
java·数据库·笔记·学习·mysql·jdbc
qq_316837751 小时前
java 对接支付宝支付 提现操作
java·linux·python
晓13131 小时前
后端篇——第三章 JavaWeb-Springboot入门
java·spring boot