ArrayList实现扩容的具体过程

ArrayList是如何实现动态扩容的


前言

我们都知道ArrayList是基于动态数组的实现,当需要的长度大于自身最大的容量的时候,能自己扩容。那么它是怎么扩容的呢?每次扩容多少呢?还有初始的长度是多少呢?


一、我们先通过了解一下ArrayList的构造方法

在这里插入图片描述
从图中我们可以看到ArrayList有三个构造方法。
一个无参构造方法,一个传入一个int型的构造方法,另一个传入指定的集合元素。第一个构造方法的描述是构造一个初始容量为10的空列表,那么当我们以一个无参构造方法创建一个ArrayList列表时,可以说此时的列表的最大容量为10吗?其实并不是这样的,让我们走进原码探索一下。

二、走进原码

以下面代码为例:

ArrayList<Integer> date = new ArrayList<>();
date.add(996);

在这里插入图片描述

找到ArrayList的无参构造方法:
在这里插入图片描述
等号左边的原码:

transient Object[] elementData;

等号右边的原码:

 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

结论:其实我们通过无参构造方法创建的集合是一个长度为0的数组。

data.add();

当要往数组里面添加数据时,是先会判断数组的长度是否已达到最大容量,如果发现存不了,就扩容。调用add方法如果不出现异常就一定是返回true。
在这里插入图片描述

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

调用add方法就一定是返回true,因为就算你数组已经放不下东西了,也会自动进行扩容,在不出异常的情况下(这种异常一般是关于越界问题)所以只要传进来对的数据类型的数据就不会出现添加不成功的情况。

//传入三个参数,分别是:要添加内容,存数据的数组和数组的当前存储(有效)的长度
private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length) 
            elementData = grow();	//如果已到达最大容量,存不下了,则进行扩容算法,返回一个数组
        elementData[s] = e;
        size = s + 1;
    }

那这个grow() 方法(扩容算法)到底是怎么样的呢?
原码如下:

private Object[] grow() {
        return grow(size + 1); //因为现数组已经不能再存新数据了,所以容量至少要加一
    }

转到一个含有一个int型参数的grow方法

private Object[] grow(int minCapacity) { //size+1   例如原长度为0则传入1
        int oldCapacity = elementData.length;	//旧数组的长度
        if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //判断如果传进来的数组是不是长度为0的数组
            int newCapacity = ArraysSupport.newLength(oldCapacity,
                    minCapacity - oldCapacity, /* minimum growth */
                    oldCapacity >> 1           /* preferred growth */);
            //oldCapacity>>1 意思是指向右移一位,比如oldCapacity原来的值为6,二进制:110,向右移一位:11,转为10进制为3,所以相当于oldCapacity*0.5
            //最终newCapacity的值是oldCapacity*1.5,具体来源,感兴趣的继续看原码,这里就不再说明了。
            return elementData = Arrays.copyOf(elementData, newCapacity);
            //将一个旧数组赋值到一个新长度的数组,并返回新数组
        } else {
            return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
            //如果传进来的是一个长度为0的数组,则返回在默认值和旧长度+1(minCapacity)中的最大值,而这个默认值,就是我们要寻找的10
        }
    }

在上面代码的最后一个return中,Ctrl+单击进DEFAULT_CAPACITY 原码为:

    private static final int DEFAULT_CAPACITY = 10;

所以说初始化容量为10,,,,这个10就是在这里来的。

三、扩展

在这里插入图片描述
实际上在这一步,传入的数组不为长度为0的数组时获取的newCapacity不一定是旧数组长度的1.5倍。我们Ctrl+点击上图红色的内容

public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
        // assert oldLength >= 0
        // assert minGrowth > 0
		//oldlength:旧数组的长度  minGrowth:要求至少扩容的长度  prefGrowth:原数组长度的0.5倍
        int newLength = Math.max(minGrowth, prefGrowth) + oldLength;
        if (newLength - MAX_ARRAY_LENGTH <= 0) {
        //MAX_ARRAY_LENGTH(默认值):Integer.MAX_VALUE(Integer类型最大值) - 8
        //说明比最大值小,可以直接返回扩容的长度
            return newLength;
        }
        //r如果比最小值,执行下面这一行
        return hugeLength(oldLength, minGrowth);
    }

Ctrl+点击 hugeLength:

 private static int hugeLength(int oldLength, int minGrowth) {
        int minLength = oldLength + minGrowth;
        if (minLength < 0) { // overflow  内存溢出了
            throw new OutOfMemoryError("Required array length too large");
        }
        if (minLength <= MAX_ARRAY_LENGTH) {   
       
            return MAX_ARRAY_LENGTH;
        }
        return Integer.MAX_VALUE;
        //minLength 的值就是在Integer.MAX_VALUE和Integer.MAX_VALUE-8之间,就直接返回Integer.MAX_VALUE
    }

所以说返回的不一定是原数组的1.5倍长度,也有可能因为越界抛出异常,也可能是返回长度为Integer.MAX_VALUE或Integer.MAX_VALUE-8

四、注意&总结

1、ArrayList:使用的是数组结构,对增加删除慢,查找块,所以说以后不能什么都用ArrayList来操作。
2、ArrayList<Integer> date = new ArrayList<>();
在这里确定列表的类型时,不能用int,传入的一定是包装类
3、如果使用无参构造方法,每当容量到达最大量就会自动扩容原来容量的1.5倍。如果扩容前的容量为0,则先扩容容量为10。
4、如果我们最终的需要的容量是1000, 而我们原先是用无参构造方法进行创建列表,当每次达到最大容量,就会频繁地进行数组的扩容操作,由于数组的扩容是将旧的数组的值复制到新的数组,而旧的数组就撇弃掉,从而浪费大量的内存。所以说如果初次存储时需要大量的容量,那么建议用传入int型的一参构造方法,确定初始容量,就算初始的容量不够,那么我们再扩容就是了。

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值