ArrayList源码分析

ArrayList的成员变量分析

	//序列化版本号
    private static final long serialVersionUID = 8683452581122892189L;
	
	//默认容量为10
    private static final int DEFAULT_CAPACITY = 10;
	
	//Object对象类型数组
    private static final Object[] EMPTY_ELEMENTDATA = {};
	
	//Object对象类型数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
	
	/**
	 *transient修饰的成员属性变量不会被序列化
	 *ArrayList的核心,elementData数组用于存放当前ArrayList集合的所有的元素数据
	 *意味着ArrayList底层其实就是个数组,所有add或者addAll的元素均放在elementData数组中
	 *对ArrayList的操作,其实就是对elementData数组的操作
	 */
    transient Object[] elementData; 
	
	//标记当前ArrayList的元素个数,既elementData数组中的元素个数
    private int size;

ArrayList的构造方法分析

ArrayList构造方法有三个,分别是:

  • 无参构造:ArrayList();
  • 带参构造:ArrayList(int initialCapacity),指定initialCapacity容量大小,创建ArrayList集合;
  • 带参构造:ArrayList(Collection<? extends E> c),通过一个集合,创建ArrayList集合;
    下面我们将通过代码来分析,每个构造方法均做了什么:
	/**
	 *elementData初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组
	 *此时size==0,ArrayList集合的容量大小为0
	 */
	public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
	/**
	 *此时size==0,ArrayList集合的容量大小为initialCapacity
	 */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
			//大于0,按照指定的容量initialCapacity初始化elementData 数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
			//等于0,初始化为EMPTY_ELEMENTDATA的空数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    public ArrayList(Collection<? extends E> c) {
		//把collection集合转化成数组,初始化给elementData
        elementData = c.toArray();
        //elementData初始化后元素个数不为0
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            //元素个数为0,则初始化为EMPTY_ELEMENTDATA空数组
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

通过上面三个ArrayList源码中的构造方法分析,我们可以知道
1.ArrayList集合初始化时,集合的容量capacity,也就是elementData数组的长度大小已经确定好了
2.接下来就是调用方法add等方法,来给集合增加元素的个数,存放在elementData数组中
但是此时ArrayList的情况如下:

  • 数组是长度是不可变的,也就是初始化后elementData数组的长度是固定的
  • 当存放的元素个数大于初始化时的容量时,运行时就会发生数组越界异常:java.lang.ArrayIndexOutOfBoundsException
    ArrayList实际使用中并未发生数组越界的异常,那么ArrayList实际是怎么设计的,我们接着分析ArrayList在add元素时,如果初始化容量不够用时扩容处理

ArrayList的扩容源码分析

  • 我们先创建一个ArrayList集合,初始化容量为0,此时add第一个元素,使用类似Debug模式
	//创建一个ArrayList集合,未指定初始化capacity大小,此时集合的capacity==0,元素个数size==0,elementData.length==capacity==0
	List<String> strList = new ArrayList<String>()
  • 调用add(E e)方法给集合添加元素
	//添加第一个元素
	strList.add("我是第1个元素");
  • 分析add方法源码的具体实现
    public boolean add(E e) {//e="我是第1个元素"
    	//每个元素在存放到elementData数组中前,均调用该方法
    	//方法参数值为当前elementData数组的size+1
    	//而这个方法是干什么的呢?我们接着分析该方法
        ensureCapacityInternal(size + 1); //此时size==0
        //最终把"我是元素"的元素存在放elementData数组下标为size+1的位置
        elementData[size++] = e;//elementData[0]="我是第1个元素",add成功!
        return true;
    }
  • 分析ensureCapacityInternal(int minCapacity)方法的实现
    private void ensureCapacityInternal(int minCapacity) {//minCapacity==1
    	//判断当前elementData 数组是否为初始化空数组
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//true
        	//如果是,minCapacity重新赋值为DEFAULT_CAPACITY(常量10)和minCapacity中的最大值
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//minCapacity ==10
        }
        //把minCapacity容量作为参数传递给ensureExplicitCapacity方法
        ensureExplicitCapacity(minCapacity);//minCapacity==10
    }
  • 分析ensureExplicitCapacity(int minCapacity)方法的实现
    private void ensureExplicitCapacity(int minCapacity) {//minCapacity==10
        modCount++;
        //判断当前容量minCapacity是否大于elementData数组的容量长度
        if (minCapacity - elementData.length > 0)//true
            grow(minCapacity);//ArrayList集合扩容
    }

此时整个集合add调用执行到了最后一步:
如果当前minCapacity大于elementData数组的容量长度,
调用grow(int minCapacity)方法进行扩容,否则不进行扩容
最后add的元素则直接存放在elementData[size++]下标的位置,整个add结束!

  • 分析grow(int minCapacity)方法的实现
    private void grow(int minCapacity) {//minCapacity==10
    	//获取旧的elementData数组长度
        int oldCapacity = elementData.length;//oldCapacity ==0
        //定义新的elementData数组长度容量=oldCapacity的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity==0
        //newCapacity取newCapacity和minCapacity中的最大值
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;//newCapacity == 10
        if (newCapacity - MAX_ARRAY_SIZE > 0)
        	//newCapacity 最大值限定只能为2147483647或2147483647-8
            newCapacity = hugeCapacity(minCapacity);
        //真相来了,看到此处,我们豁然开朗
        //原来ArrayList的扩容就是,通过把elementData数组按照最新的容量newCapacity ,进行数组的复制
        //然后生成新的elementData数组重新赋值给elementData数组对象,扩容结束
        //此时elementData.length==newCapacity
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
        	//int的最大值:2147483647
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

总结ArrayList集合扩容的过程

  • ArrayList集合创建时,会初试化成员elementData数组的容量capacity长度大小,既elementData.length
  • 在add元素时,元素个数size+1大于当前elementData.length时,ArrayList将进行扩容
  • 扩容标准为原elementData.length1.5倍,最大值不能超过Integer的最大值2147483647
  • 扩容过程实际就是elementData数组按照新的capacity,进行数组的复制,生成1个新elementData数组,替换掉旧的elementData数组

ArrayList使用注意事项

通过上面ArrayList的源码分析,我们知道了ArrayList集合在添加元素过程中,会因为元素的数量超过自身的容量限制,进行强行扩容
扩容结果影响:1消耗更多的时间,2创建新的数组对象

    public static void main(String[] args){
        List<String> strList = new ArrayList<String>();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            strList.add(i+"");
        }
        System.out.println("未指定容量花费时间:"+(System.currentTimeMillis()-start)+"ms");
        List<String> strList1 = new ArrayList<String>(10000000);
        start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            strList1.add(i+"");
        }
        System.out.println("指定容量花费时间:"+(System.currentTimeMillis()-start)+"ms");
    }

上面代码经过测试后结果是:
在这里插入图片描述
因此,日常在需要使用ArrayList存储大量元素时,最好是预先根据数据量指定集合的初始化容量大小,避免ArrayList的扩容影响代码执行性能,同时创建大量数组对象

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值