java中ArrayList集合扩容详解

文章详细介绍了Java中ArrayList的扩容机制,从无参构造到有参构造,分析了在添加元素时如何进行动态数组的扩容,特别是`System.arraycopy`方法在数组复制中的应用,以及ArrayList内部如何确保容量。还探讨了扩容策略,如默认容量和1.5倍扩容规则。
摘要由CSDN通过智能技术生成

 普通数组的复制

了解前要首先知道ArrayList底层使用的动态数组,我们先想一下,数组的扩容是怎么样进行的,无非就是创建一个新的数组(数组的长度要大于原数组),然后将新数组赋值给原数组的过程,ArrayList也是使用这样的机制来实现的。

        接下来我们看一下自定义数组扩容是如何实现的

int[] ints = new int[]{1,2,3,4,5,6,7};    //初始化一个长度为7的int类型的数组
System.out.println("原数组数据:"+Arrays.toString(ints));
int[] newInts = new int[12];    //在重新创建一个数组长度要比原数组的长度大
//将原数组赋值给新数组
System.arraycopy(ints,0,newInts,0,Math.min(ints.length,newInts.length));
System.out.println("新数组数据"+Arrays.toString(newInts));

ints = newInts;     //将新数组的地址赋值给原数组
System.out.println("原数组数据:"+Arrays.toString(ints));

/**
输出结果:
原数组数据:[1, 2, 3, 4, 5, 6, 7]
新数组数据[1, 2, 3, 4, 5, 6, 7, 0, 0, 0, 0, 0]
原数组数据:[1, 2, 3, 4, 5, 6, 7, 0, 0, 0, 0, 0]
*/
System.arraycopy方法详解:

该方法是System类 中的一个方法,其主要的功能就是数组的赋值。

public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);
参数位置的含义:
     第一个参数src:     原数组
     第二个参数srcPos: 原数组起始位置(从这个位置开始复制)
     第三个参数dest:   目标数组
     第四个参数destPos:目标数组粘贴的起始位置
     第五个参数length: 复制的个数

 ArrayList无参构造

首先我们来看一下ArrayList的无参构造

//ArrayList底层的数组
transient Object[] elementData;   
//实例化一个长度为0的数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

private static final int DEFAULT_CAPACITY = 10;

private int size;


public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
ArrayList无参构造主要就是在底层创建一个长度为0的Object[]的数组,数组变量为elementData。

接下来我们看一下add方法里面的内容

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

可以看到我们使用add方法需要先执行ensureCapacityInternal(size + 1);这段代码,先分析一下size + 1 等于多少? size的初始值是0,size + 1 = 1;

接下来我们在IDEA中按着Ctrl + 鼠标左键 进入方法中,方法的形参 minCapacity = 1 ,进入方法。

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

if语句中判断的是ArrayList底层的数组是否是一个空数组,由于我们在初始化的时候没有给数组赋值和初始容量,所以此时if语句中的条件为true。取DEFAULT_CAPACITYminCapacity的最大值赋值给minCapacity变量,接下来执行ensureExplicitCapacity(minCapacity);进入方法体后我们可以看到。

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

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

modCount的初始值是 0 ,该变量在AbstractList抽象类中定义的。接下来的判断语句,想必大家一定能看的懂!!!判断ArrayList底层数组的大小和minCapacity的值进行比较,很显然为true,进入grow(minCapacity) ,此时调用的grow(minCapacity)相当于 grow(10)。

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    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);
    }

我们一行一行的分析。

int oldCapacity = elementData.length;这一行表示获取到ArrayList底层数组的长度 ---> 这里是0
int newCapacity = oldCapacity + (oldCapacity >> 1);(这行代码的意思就是底层数组的原长度扩大到原来的一半(比如以前长度是12,现在是18),但是这里是 0 )
if (newCapacity - minCapacity < 0)newCapacity = minCapacity;这行代码的意思就是将grow方法中的参数赋值给newCapacity,此时newCapacity的值等于 10 。
if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);这里判断newCapacity的值是否大于int类型的最大值减去8,很显然不大于(一般来说数组的长度都不会大于这个值)
elementData = Arrays.copyOf(elementData, newCapacity);这里就是将原数组进行复制并扩容,进入Arrays.copyOf这个方法中。
    public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }

    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

 Arrays.copyOf这个方法底层就是调用的System.arraycopy方法,Arrays.copyOf方法的两个参数一个是原数组,一个是新数组的长度,扩容后将扩容后的数组赋值给原数组,所以ArrayList底层的elementData变量表示的数组的长度就变成了newCapacity表示的值,上面newCapacity的值是10,所以elementData变量表示的数组长度就进行了扩容。然后以此返回到ArrayList中的add()方法。

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  
        elementData[size++] = e;
        return true;
    }

接下来执行elementData[size++] = e;这行代码,上面的一系列操作我们并没有给这个size赋值,所以这里的size还是 0 ,将我们需要添加的元素 e  放在elementData表示的数组的第size个位置,然后size的大小加1,此时add方法执行完毕。之后我们再往数组中添加元素的时候还是这个流程,如果不需要扩容size的值加1,需要扩容的话就带带着size+1的值进入ensureCapacityInternal(int minCapacity)方法,在进入ensureExplicitCapacity(int minCapacity)方法,在进入grow(int minCapacity)方法进行扩容,这几个方法里面的逻辑都是和上面一样的,进行扩容之后在以此返回到add()方法,执行elementData[size++] = e;这行代码将元素插入到elementData表示的数组中。

以上的扩容方式是ArrayList的无参构造的时候的逻辑,有参构造就很有意思了,如果你无参构造了解扩容的流程的话,有参构造就很容易分析了。

接下来我们分析有参构造。

ArrayList有参构造

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

可以看到我们使用有参构造的时候就是给底层的elementData表示的数组初始化容量,扩容方式和上面的无参构造的时候一样。只不过是,使用无参构造的时候默认的扩容是扩容到10。

扩容都是1.5倍的扩容

比如我们是ArrayList<Object> arrayList = new ArrayList<>(8);当我们的数组需要进行扩容的时候,会扩容到12 (8 + 4)。

非常感谢各位大佬看到这里,以上都是我自己分析源码得到了,希望能帮的到你。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@沉住气

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值