【Java】ArrayList源码解析

学而不思则罔,思而不学则殆


引言

ArrayList是一个很常见的列表工具类,虽然是列表,但它的底层原理却是数组。

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

存储ArrayList元素的数组缓冲区。ArrayList的容量是这个数组缓冲区的长度。任何带有elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList将在添加第一个元素时扩展为DEFAULT_CAPACITY。

测试添加元素

测试代码

通过反射拿到elementData的数组

    private static void show() {
        System.out.println("\n--------------------------");
        Class<ArrayList> aClass = ArrayList.class;
        try {
            Field field = aClass.getDeclaredField("elementData");
            field.setAccessible(true);
            Object[] objects = (Object[]) field.get(arrayList);
            System.out.println("size:" + arrayList.size());
            System.out.println("length:" + objects.length + " elementData:" + Arrays.toString(objects));
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
        System.out.println("--------------------------\n");
    }

测试空列表

初始化一个列表

private static ArrayList<Integer> arrayList = new ArrayList<>();

打印信息如下:

size:0
length:0 elementData:[]

通过反射拿到DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的对象看一下:

        try {
            Field field = aClass.getDeclaredField("elementData");
            Field empty = aClass.getDeclaredField("DEFAULTCAPACITY_EMPTY_ELEMENTDATA");
            field.setAccessible(true);
            empty.setAccessible(true);
            Object[] objects = (Object[]) field.get(arrayList);
            Object[] default_empty = (Object[]) empty.get(null); //静态变量通过null获取
            System.out.println("default_empty:" + default_empty);
            System.out.println("objects:" + objects);
            System.out.println("size:" + arrayList.size());
            System.out.println("length:" + objects.length + " elementData:" + Arrays.toString(objects));
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

打印结果:

default_empty:[Ljava.lang.Object;@7ea987ac
objects:[Ljava.lang.Object;@7ea987ac
size:0
length:0 elementData:[]

两个是同一个数组对象,说明空数组的时候,采用了:DEFAULTCAPACITY_EMPTY_ELEMENTDATA

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

添加一个元素

        arrayList.add(0);
        show();

打印数组信息:

size:1
length:10 elementData:[0, null, null, null, null, null, null, null, null, null]

list的size=1
底层数组的长度为10
其他九个位置为null,数据为空


    /**
     * 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;
    }
    //第一次添加元素的是否返回DEFAULT_CAPACITY = 10
    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));
    }

数组长度:0 ->10

测试数组第二次扩容

此时数组长度为10,我们把数组填满。

        for (int i = 1; i < 10; i++) {
            arrayList.add(i);
        }
        show();

打印一下此时的数组情况:

size:10
length:10 elementData:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

size为10,数组长度也为10,再添加一个元素数组就放不下了?那要怎么处理呢?我们都知道ArrayList是一个可变的数组,怎么可变的呢?
我们先看看扩容长度怎么变化:
测试:在上面的基础上再添加一个元素,在打印数组情况

        arrayList.add(11);
        show();

情况如下:

size:11
length:15 elementData:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, null, null, null, null]

此时size=11,数组长度变为15了,刚刚加入了11,后面四个是null,空位置

再看源码,基于之前的:

    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);
    }
    
    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);
    }
  1. 在调用ensureCapacityInternal方法的时候,size=10 所以入参 = 11
  2. calculateCapacity此时返回的是11
  3. ensureExplicitCapacity方法入参是11,此时数组长度10,所以会走grow方法,入参是11
  4. 再来看看关键方法grow,入参11
  5. 其中oldCapacity = 10,老的数组长度 新的数组长度= oldCapacity +oldCapacity/2 = 15 (跟前文的15对上了)
  6. 然后是一些判断逻辑,先不用关心,主要最后一行:数组复制,浅拷贝。

Arrays.copyOf 简单测试:

        int[] ints = new int[]{1, 2, 3, 4};
        int[] ints1 = Arrays.copyOf(ints, 8);
        int[] ints2 = Arrays.copyOf(ints, 3);
        System.out.println(Arrays.toString(ints));//[1, 2, 3, 4]
        System.out.println(Arrays.toString(ints1));//[1, 2, 3, 4, 0, 0, 0, 0]
        System.out.println(Arrays.toString(ints2));//[1, 2, 3]

想了解更多Arrays相关的方法,访问:【Java】Arrays

结束了就修改了elementData 数组长度,做了一次数组拷贝。
理论上会有数组的操作,操作时间复杂度为:
O ( n ) O(n) O(n)

ArrayList扩容长度总结

这里只是默认不设置长度的情况。
L e n g t h [ i ] = { 10 i=0 L e n g t h [ i ] = L e n g t h [ i − 1 ] + L e n g t h [ i − 1 ] / 2 i>0 Length[i]= \begin{cases} 10 & \text{i=0}\\ Length[i] = Length[i-1]+Length[i-1]/2 & \text{i>0}\\ \end{cases} Length[i]={10Length[i]=Length[i1]+Length[i1]/2i=0i>0

举例:

0
10
15 = 10 + 10/2 = 10 + 5
22 = 15 + 15/2 = 15 + 7
33 = 22 + 22/2 = 22 + 11
49 = 33 + 33/2 = 33 + 16

长度扩容验证

前面我们长度是15了

15 --》22

        //填充
        arrayList.add(12);
        arrayList.add(13);
        arrayList.add(14);
        arrayList.add(15);
        show();
        //再加一个元素,数组长度扩容
        arrayList.add(15);
        show();
--------------------------
size:15
length:15 elementData:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15]
--------------------------

--------------------------
size:16
length:22 elementData:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 15, null, null, null, null, null, null]
--------------------------

22 --》33

        //填充
        for (int i = 16; i < 22; i++) {
            arrayList.add(i);
        }
        show();

        //扩容
        arrayList.add(22);
        //扩容
        show();
--------------------------
size:22`在这里插入代码片`
length:22 elementData:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 15, 16, 17, 18, 19, 20, 21]
--------------------------


--------------------------
size:23
length:33 elementData:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 15, 16, 17, 18, 19, 20, 21, 22, null, null, null, null, null, null, null, null, null, null]
--------------------------

测试删除元素

先填充元素,size = 15

        for (int i = 0; i < 15; i++) {
            arrayList.add(i);
        }
        show();
--------------------------
size:15
length:15 elementData:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
--------------------------

此时size和length都是15

删除0下标位置的元素

        arrayList.remove(0);
        show();
size:14
length:15 elementData:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, null]

看看源码:

    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

其中最重要的就是:

System.arraycopy(elementData, index+1, elementData, index, numMoved);

表示把[index+1,size)的所有元素都向前移位,并把size-1的位置元素置为null.
时间复杂度为: O ( n u m M o v e d ) O(numMoved) O(numMoved)

更多arraycopy用法详见:【Java】System.arraycopy

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值