ArrayList 原理

成员变量和构造方法

private static final Object[] EMPTY_ELEMENTDATA = {};

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
  1. 定义了两个空数组成员变量,这两个成员变量除了名字不一样,别的都是一样的
  2. 虽然能根据源码看出使用不同的构造方法创建 ArrayList 时,会使用不同的变量,但是为什么不定义一个,两个构造方法都是用这个变量呢?原因是少扩容
    1. DEFAULTCAPACITY_EMPTY_ELEMENTDATA 在第一次 add 的时候会创建一个长度是 10 的数组,当元素数量 <= 10 时不会触发扩容
    2. EMPTY_ELEMENTDATA 不会创建 10 个长度的数组,就是空数组,所以相比于 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 扩容次数更多
    3. 每次扩容是 1.5 倍,并且还是 static 修饰的(共享变量),长度也只是 10,这么一点点内存都要省下来,简直煞费苦心用心良苦!!!

添加元素和扩容

public boolean add(E e) {
    modCount++; // 修改次数+1
    // 再调用本来的 add 完成元素的添加
    add(e, elementData, size); 
    return true;
}

// 三个参数分别是:添加的元素、list的数组、list的元素个数
private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length) // 如果 list.size 等于数组长度,就扩容(size 是元素个数,size 一定 <= 数组长度)
        elementData = grow(); // 扩容方法,返回扩容后的数组,内部也把原来数组的数据拷贝到这个新数组了
    elementData[s] = e; // 元素放入数组
    size = s + 1; // list 容量+1
}

// grow 方法再调用另一个 grow 方法
private Object[] grow() {
    return grow(size + 1); // size => list 元素个数(为什么要加1,添加了一个元素后 size 就要增加 1)
}

// minCapacity 是新 size
private Object[] grow(int minCapacity) {
    int oldCapacity = elementData.length; // 数组长度
    // 如果数组元素不为 0 也就是目前已经保存的有元素了,或者 elementData 不为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 就走这里
    // 两种情况:1,有参构造(参数是0)创建的 ArrayList 初次添加元素;2:数组已经有元素了
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // 扩容,得到一个新的数组长度(前面 add 方法有条件别忘了,就是数组长度和list.size相同)
        int newCapacity = ArraysSupport.newLength(oldCapacity,
                minCapacity - oldCapacity, /* minimum growth */
                oldCapacity >> 1           /* preferred growth */);
        // 拷贝原来的元素到新的数组中
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } else {
        // 只有一种情况会走这里,使用无参构造创建的 ArrayList 初次添加元素,DEFAULT_CAPACITY 是 10,所以这里会创建要给长度是 10 的数组
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}

具体怎么得到新的长度的,新的长度是多少?关键是这行代码

// oldCapacity:list 原 size(传来的是原数组长度,也就是原 size,因为前提条件是数组长度 == size)
// minCapacity:list 新 size(新 size 比原 size 大 1,因为增加的是一个元素)
// oldCapacity >> 1:右运算,减少一半(list 原 size 的一半)
int newCapacity = ArraysSupport.newLength(oldCapacity,
                minCapacity - oldCapacity, /* minimum growth */
                oldCapacity >> 1           /* preferred growth */);

ArraysSupport.newLength 具体做了什么

// 三个参数分别是:list 原 size、1、list 原 size 的一半
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {

	// Math.max(minGrowth, prefGrowth) 取大的是 prefGrowth(也就是原 size 的一半)
    // 所以 prefLength = 原 size + 0.5 * 原 size,也就是原 size 的 1.5 倍,这就是新的数组长度
    int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
    // SOFT_MAX_ARRAY_LENGTH 是个静态常量 Integer.MAX_VALUE - 8,在 Integer.MAX_VALUE 附近不同的 vm 规范不一致,为了保险起见设置一个软最大长度
    if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
        // 返回原 size 的 1.5 倍
        return prefLength;
    } else {
        // 新数组长度如果大于 Integer.MAX_VALUE - 8
        return hugeLength(oldLength, minGrowth);
    }
}

private static int hugeLength(int oldLength, int minGrowth) {
    // 就不扩容了,因为已经达到 Integer.MAX_VALUE - 8,再来个 1.5 倍肯定超过 Integer.MAX_VALUE,直接原 size + 新元素个数
    int minLength = oldLength + minGrowth;
    // 如果大于 Integer.MAX_VALUE 抛出 OOM(Integer.MAX_VALUE + 1 就是一个负数了)
    if (minLength < 0) { // overflow
        throw new OutOfMemoryError(
            "Required array length " + oldLength + " + " + minGrowth + " is too large");
    } else if (minLength <= SOFT_MAX_ARRAY_LENGTH) {
        return SOFT_MAX_ARRAY_LENGTH;
    } else {
        return minLength;
    }
}

批量添加元素

public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray(); // 新元素转数组
    modCount++; // 修改次数+1
    int numNew = a.length; // 新元素个数
    if (numNew == 0) // 扯犊子
        return false;
    Object[] elementData;
    final int s;
    // list 里的原数组是否能容纳下 numNew,如果装不下就扩容,如果装的下直接放(数组长度 >= list.size)
    if (numNew > (elementData = this.elementData).length - (s = size)) 
        elementData = grow(s + numNew); // 扩容方法
    System.arraycopy(a, 0, elementData, s, numNew); // 拷贝
    size = s + numNew; // 维护 size
    return true;
}
posted @ 2023-05-24 17:04  CyrusHuang  阅读(12)  评论(0编辑  收藏  举报