ArrayList部分源码

深入剖析ArrayList的构造函数、初始化过程、扩容机制、增删操作及关键属性,揭示其内部实现细节。
//默认初始容量
private static final int DEFAULT_CAPACITY = 10;
//空数组,有参构造函数,参数为0时,将elementData数组赋值为EMPTY_ELEMENTDATA
private static final Object[] EMPTY_ELEMENTDATA = {};
//空数组,无参构造函数时,将elementData数组赋值为该空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; 

初始化
通过构造函数进行初始化
1.无参构造函数

    //无参构造函数
public ArrayList() {
//将底层数组Object[] elementData赋值为默认空数组,此时数组容量为0,只有add数据时,才会分配默认初始容量10.
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

将elementData赋值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA(默认空数组),虽然ArrayList()的默认初始容量为10,但这里数组为空,此时调用size(),结果为0。
常出问题的点:

List<List<Integer>> list = new ArrayList<>();
list.get(1).add(0);
//错!因为此时为空数组,就越界了(可能没有人像我这么蠢吧……)

2.有参构造函数

//有参构造函数
public ArrayList(int initialCapacity) {
//判断参数大小
        if (initialCapacity > 0) {
        //
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
        //初始容量等于0,赋值为一个空数组。
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

判断参数大小,对elementData数组进行初始化。
这里!虽然对数组大小进行了初始化,但是list没有,此时size()还是为0!
看,依然越界
在这里插入图片描述

3.有参构造函数,参数为collection接口
先将传入参数toArray(),如果得到的数组长度不为0,那么判断数组类型是否是Object[]类型,因为ArrayList的底层是Object[]数组,如果不是,则使用Arrays.copyOf(…)的方法进行拷贝,内部会创建一个Object[]类型的数组,长度为size,进行拷贝,使得得到的elementData是Object[]数组。

    //List<Integer> path = new ArrayList<>();
    //List<List<Integer>> list = new ArrayList<>(path);
public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

这里toArray()调用的时Collection接口的toArray()方法,具体调用哪个类的toArray()方法应该是看c的实现类。
ArrayList里的toArray()方法:
将elementData数组中的元素拷贝到长度为size的Object[]数组中,并返回该数组。

public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

接收T类型的数组,返回一个T类型的数组
主要对比a.length和list中的元素个数size()
如果a.length<size,使用Arrays.copyOf方法进行拷贝,创建一个长度为size的新数组接收elementData中的元素, 之前传入的数组a已经没用了。
如果a.length >= size,直接调用System.arraycopy方法,将elementData中的元素拷贝到a中。

public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

新增
add(E e)

public boolean add(E e) {
//先判断容量是否足够,不够需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //添加数据
        elementData[size++] = e;
        return true;
    }
private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

这里! 如果是第一次add数组,那么elementData就为默认空数组,此时返回默认容量和最小容量的最大值(其实应该就是返回的默认容量吧?毕竟size==0,size+1 == 1 < 10? 但我感觉我理解的有错)

private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
 private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
        //容量不够,跳去扩容
            grow(minCapacity);
    }

add(int index,E element)

public void add(int index, E element) {
//判断索引是否在范围内,如果index>size||index<0,抛异常。
        rangeCheckForAdd(index);
//判断容量是否足够
        ensureCapacityInternal(size + 1);  // Increments modCount!!
 //将elementData中[index,elementData.length-1]这个区间上的数拷贝到elementData的[index+1,index+1+size-index-1]区间上。
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
       //将元素赋值给index位置上。
        elementData[index] = element;
        size++;
    }

扩容
新容量 = 原容量+原容量/2;
如果新容量<需要容量,那么新容量=需要容量
如果新容量>数组最大大小,那么新容量=hugeCapacity(minValue)
然后将原数组元素复制到新数组。
(下次一定用个好看的工具画图)
在这里插入图片描述

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //1.8之后,采用位运算,右移一位,效率更高了。
        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;
    }

删除
public E remove(int index)
其实add(int index, E element)的原理是一样的,就是数组复制。

public E remove(int index) {
//判断索引是否越界
        rangeCheck(index);

        modCount++;
        
        E oldValue = elementData(index);
//将[index+1,size-1]的数据复制到[index,size-2],然后将size-1位置上的值置空。
        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;
    }

参考资料
1.源码
2.https://blog.youkuaiyun.com/qq_35190492/article/details/103883964
3.https://blog.youkuaiyun.com/littlehaes/article/details/105552512

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值