Java源码——ArrayList源码解析

1 类定义

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

ArrayList继承AbstractList类,实现了List、RandomAccess、Cloneable、Serializable四个接口。
实现RandomAccess接口,可以实现随机访问。
实现Cloneable,并重写了clone方法,实现ArrayList的拷贝。
实现Serializable接口,可以实现序列化。

2 clone复制

public Object clone() {
    try {
        ArrayList<?> v = (ArrayList<?>) super.clone();
        v.elementData = Arrays.copyOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}

是一种浅复制,数组中的对象只是复制了引用,并没有复制对象本身。

3 构造方法

在这里插入图片描述
存在三个构造方法

public ArrayList() {
   this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

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

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中的Object数组为空数组。提供容量的构造可以指定初始化容量,如果为0则和空参构造一样,小于0则报异常。还有一个构造方法是将整个Collection赋值给ArrayList。

4 添加元素

在这里插入图片描述
add也有三个方法

public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}

public void add(int index, E element) {
    rangeCheckForAdd(index);
    modCount++;
    final int s;
    Object[] elementData;
    if ((s = size) == (elementData = this.elementData).length)
        elementData = grow();
    System.arraycopy(elementData, index,
                     elementData, index + 1,
                     s - index);
    elementData[index] = element;
    size = s + 1;
}

第一个调用第二个,如果到达容量的最大值,则扩充容量,如果没有则在数组中添加。第三个方法是在指定索引处添加元素,首先校验索引的合法性,也是如果达到容量最大值则扩充容量,然后将索引后面的所有元素往后挪一位,并插入当前要插入的元素。

5 容量增长

private Object[] grow() {
    return grow(size + 1);
}

private Object[] grow(int minCapacity) {
    int oldCapacity = elementData.length;
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        int newCapacity = ArraysSupport.newLength(oldCapacity,
                minCapacity - oldCapacity, /* minimum growth */
                oldCapacity >> 1           /* preferred growth */);
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } else {
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}

首先看第一种情况,无参构造,第一次添加元素时。此时minCapacity=1,oldCapacity=0,elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA,所以会走else。
DEFAULT_CAPACITY=10,所以会新建一个10个元素大小的Object数组。
然后看第二种情况,就是无参构造,并且已经达到最大容量10了怎么扩容。
此时调用ArraysSupport.newLength计算新容量

public static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
    // preconditions not checked because of inlining
    // assert oldLength >= 0
    // assert minGrowth > 0

    int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
    if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
        return prefLength;
    } else {
        // put code cold in a separate method
        return hugeLength(oldLength, minGrowth);
    }
}

oldLength是扩容前的最大容量,minGrowth固定值为1,prefGrowth是oldLength的一半,因为之前采用了右移运算符计算。
因为如果oldLength很大的话,再加上自己的一半可能会溢出,所以后面加了判断,如果在合法的范围内,则直接返回oldLength加他的一半,即按1.5倍扩容。判断合法的范围是1到Integer最大值减去8。
如果溢出了则调用hugeLength

private static int hugeLength(int oldLength, int minGrowth) {
    int minLength = oldLength + minGrowth;
    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;
    }
}

这就是每次扩容只是容量加1。
总结:
1、ArrayList空参构造,没有初始化容量,初始容量为0
2、添加第一个元素时初始化容量为10
3、当添加元素达到数组容量上限时开始扩容,默认扩容1.5倍。
4、如果扩容1.5倍超出最大上限Integer最大值-8,则扩容量为1
5、如果超过Integer最大值,则报错误

6 代码示例

6.1 无参构造

import java.util.List;

public class ArrayLIstDemo {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        try {
            System.out.println(getCapacity(list));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static int getCapacity(ArrayList<Integer> list) throws NoSuchFieldException, IllegalAccessException {
        Class<? extends List> listCls = list.getClass();
        Field field = listCls.getDeclaredField("elementData");
        field.setAccessible(true);
        Object[] objects = (Object[]) field.get(list);
        return objects.length;
    }
}

通过反射来获取ArrayList容量。jdk17中,会报异常,原因是高版本的jdk中只允许反射public的成员,需要加上虚拟机参数:

--add-opens
java.base/java.lang=ALL-UNNAMED
--add-opens
java.base/java.util=ALL-UNNAMED
--add-opens
java.base/java.nio=ALL-UNNAMED
--add-opens
java.base/sun.nio.ch=ALL-UNNAMED

上面的执行结果为

0

添加第一个元素

list.add(123);

输出结果为

10

验证了无参构造后,第一次添加元素时,初始化为默认大小10。
添加11个元素到list中,查看容量,输出为

15

扩容为1.5倍

6.1 初始化容量构造

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

输出容量为

3

添加4个元素,输入容量为

4

6.3 扩容优化

如果能够预估使用的数组容量,可以先调用ensureCapacity初始化一个特定的数组容量,避免ArrayList频繁扩容,ArrayList的扩容涉及到数组拷贝到一个扩容后的新数组,比较消耗性能。

7 ArrayList的序列化问题

ArrayList实现了Serializable接口,可以实现序列化,而存储元素的Object数组却是用transient修饰的。
实现Serializable接口的类,在序列化时是自动的,而被transient修饰的变量不会被序列化。那么ArrayList的序列化是通过什么实现的呢?
ArrayList拥有自己的writeObject和readObject方法,可以实现存储元素的序列化。

transient Object[] elementData;

@java.io.Serial
private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException {
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();

    // Write out size as capacity for behavioral compatibility with clone()
    s.writeInt(size);

    // Write out all elements in the proper order.
    for (int i=0; i<size; i++) {
        s.writeObject(elementData[i]);
    }

    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

@java.io.Serial
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {

    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in capacity
    s.readInt(); // ignored

    if (size > 0) {
        // like clone(), allocate array based upon size not capacity
        SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size);
        Object[] elements = new Object[size];

        // Read in all elements in the proper order.
        for (int i = 0; i < size; i++) {
            elements[i] = s.readObject();
        }

        elementData = elements;
    } else if (size == 0) {
        elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new java.io.InvalidObjectException("Invalid size: " + size);
    }
}

通过ObjectOutputStream序列化ArrayList的时候,会调用到WriteObject方法,首先写入数组个数,然后逐个写入元素。读取也是一样的道理。
不采用原生的序列化原因应该是为了减少空间占用,因为Object数组的容量和实际存储的元素个数size通常不一致。

8 总结

  • ArrayList继承AbstractList,实现了List接口
  • 实现了RandomAccess接口可实现随机访问
  • 实现了Cloneable接口,并重写了clone方法,实现元素的浅拷贝
  • 实现了Serializable接口,可序列化,序列化通过自定义writeObject、readObject来实现序列化,可以减少空对象的序列化。
  • ArrayList无参构造创建的Object数组是0长度的,第一次add时,初始化为默认长度10。
  • ArrayList可以使用指定长度的构造方法来创建一个指定长度的数组。
  • ArrayList通常扩容策略是1.5倍扩容,如果1.5倍扩容后大于最大容量或者溢出,则每次扩容一个。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值