ArrayList源码解读

一、前言

ArrayList 即 List 的动态数组实现,底层实现机制就是动态数组,且支持动态扩容。
特点是:

  1. 非线程安全
  2. 底层实现是数组,即集合中的元素是内存连续的,可通过元素索引下标 index 实现 O(1) 时间内的随机访问
  3. 集合中元素是有序可重复的,支持 NULL;
  4. 集合能根据需要自动扩容

ArrayList 中元素是内存连续的,因此根据索引下标 index 随机访问或遍历性能较高;但涉及到元素随机插入删除(元素插入触发扩容时会造成所有元素移动,从中间删除会涉及到后续所有元素移动)较多的场景性能较差

二、ArrayList 源码解读

2.1 构造方法初始化

默认无参构造方法

这是我们最常用的场景,调用无参构造函数实例化 ArrayList 对象后,内存实际指向的是一个空数组,此时集合 capacity(容量)为 0

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

指定 initialCapacity(集合初始容量)参数构造方法

即此时会新开辟一块容量大小为 initialCapacity 的连续内存空间
注意:如使用 ArrayList 时明确知道会需要较大容量,此时可通过在构造函数初始化时使用此方法指定初始容量,减少自动扩容移动元素次数对性能的影响

/**
 * 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);
    }
}

指定 c(集合)参数构造方法

即将一个集合 c 作为参数构造 ArrayList 实例,此时会开辟一块连续内存用于存储集合中元素,ArrayList 实例 capacity(容量)大小为集合 c 长度;

/**
 * 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;
    }
}

2.2 添加元素 add 方法

注意,当使用无参构造函数实例化 ArrayList 对象时,默认是个空数组容量为0,在首次调用 add 方法添加元素时才会自动扩容为 10(即 对应 DEFAULT_CAPACITY参数值 )。
这一点在面试时候需特别注意,面试官问 ArrayList 初始默认容量大小,如果直接回答说 10 容易给面试官留下知识点混淆不清的印象。
此时标准答案可以说:ArrayList 使用无参构造函数实例化时初始容量是 0,在首次调用 add 方法添加元素时会进行第一次扩容,且大小默认为 10

/**
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;
/*
*
 * 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);
}

2.3 ArrayList 扩容

什么时候触发扩容

当 minCapacity - elementData.length > 0 时,会调用 grow 方法触发扩容;
其中,
minCapacity 即集合最小容量(当前集合长度 size + 要添加元素个数),添加单个元素即 minCapacity = size + 1;
elementData.length 即底层数组长度大小;

下面代码有2个关键点需要注意:

  1. 无参构造实例化 ArrayList 对象后首次添加一个元素时,此时 minCapacity = 0 + 1,elementData.length = 0,即会触发第一次扩容,此时扩容容量为 10,这个默认长度设值要结合 grow 方法实现;
  2. 触发扩容时机要注意;第一次扩容后 elementData.length = 10,此时要第一次满足 minCapacity - elementData.length > 0 时 minCapacity 值为 11,就是达到当前容量 10 后再添加元素时会触发再次扩容,即扩容触发时机是:当前元素数量达到数组长度后,会在下一次添加元素时触发扩容。
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

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

扩容方法 grow 实现

int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
oldCapacity >> 1 右移一位,即最后一位舍弃,其他位向右移动一位,相当于 oldCapacity / 2(举例:10 / 2 = 5, 15 / 2 = 7);
因此当问到 ArrayList 扩容时候会扩容到多大呢?
标准答案可以说:集合扩容后新容量,应为集合数据当前容量再加上集合数据当前容量大小的一半大小(向下取整);

集合扩容后新容量 = 集合数据当前容量 + 集合数据当前容量 / 2;

第 n 次扩容集合扩容后新容量
10
10 + 10 / 2 = 15
15 + 15 / 2 = 22
22 + 22 / 2 = 33
33 + 33 / 2= 49
49 + 49 / 2= 73

你可能会突然想到,首次扩容时 elementData.length 为 0,套用上述公式计算出的扩容后新容量还是 0,这不就是一个反例吗?
是的,在首次扩容 elementData.length 为 0 时这样计算当然不对,因此代码后续还有个重新判断赋值;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;}
首次扩容时,newCapacity 根据上述扩容公式计算出为 0,minCapacity 为设置的默认容量 10,此时判断成立即进行一次重新赋值,newCapacity = minCapacity = 10,这也是我们说首次扩容后容量大小为 10 的原因所在

后续代码还有个判断 newCapacity - MAX_ARRAY_SIZE > 0,这是在容量达到 MAX_ARRAY_SIZE(MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8)时候的特殊处理,容量达到整型上限场景很少,我们大概看下实现即可无需过多关注此处省略;

/**
 * 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值