java源码浅析之ArrayList扩容机制

前言

准备面试,从源码上看一下ArrayList的扩容机制

扩容机制

底层结构

就是一个Object数组,那么意味着对其初始化一个固定长度后,就不能改变了,要想改变,只能复制到新的数组中
在这里插入图片描述

初始化

三种方式,只需要了解前两种

    private static final Object[] EMPTY_ELEMENTDATA = {};
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    transient Object[] elementData;
    
    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);
	    }
	}
	
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
    public ArrayList(Collection<? extends E> c) {
        Object[] a = c.toArray();
        if ((size = a.length) != 0) {
            if (c.getClass() == ArrayList.class) {
                elementData = a;
            } else {
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // replace with empty array.
            elementData = EMPTY_ELEMENTDATA;
        }
    }

第一种

  • 传入一个cap,并且为其new一个相应大小的Object数组
  • cap为零则和第二种一样

第二种

  • 不传参数
  • this.elementData={}
  • 空数组意味着没有空间添加元素,如果要添加元素则必须扩容

添加元素(add方法)

add方法同样有两种,主要看直接插入数组末尾的

    public boolean add(E e) {
    	// 只需要保证数组长度为size+1,这样就能添加一个了
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
    	// 判断一下elementData是否为空,是的话就返回默认容量,DEFAULT_CAPACITY=10
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        // minCapacity表示我至少需要多少容量,elementData.length表示我目前有多少容量
        // 如果目前容量能满足我的需求,则无需扩容,否则扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }    
    
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        // 增加当前容量的一半作为新的容量
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 拷贝到新的数组中
        elementData = Arrays.copyOf(elementData, newCapacity);
    }    
  • 源码很简单,只需要思考几个问题
  • 无参初始化的数组容量是多少?
    • 初始化时为空数组
    • 添加元素时会进行扩容,默认最小容量为10
  • 什么时候会扩容?
    • 初始化后不会扩容
    • 添加元素时,如果需要的容量大于现有数组容量则扩容
  • 怎么扩容?
    • 计算新容量,为原来容量加上其一半
    • 如果新容量大于需求容量,则扩容为新容量,否则直接以需求容量进行扩容(这意味着下次添加第二个元素后又会触发扩容)
  • 注意事项
    • 由上述扩容方式可知,容量较小时,若需要不断添加元素,会频繁触发扩容,因此对于可预知的元素长度,最好预设Cap进行初始化

至此,扩容原理梳理结束

例子

举个例子吧,在例子中,使用反射来获取elementData的容量

    public static void arraylistCheck() throws NoSuchFieldException, IllegalAccessException {
        System.out.println("case 1: ");
        ArrayList<String> arr = new ArrayList<>();
        System.out.println("初始化后:"+getCap(arr));
        arr.add("11");
        System.out.println("add 1次后:"+getCap(arr));
        for (int i = 0; i < 13; i++) {
            arr.add("11");
        }
        System.out.println("add 13次后:"+getCap(arr));
        System.out.println("-----------------------------");
        System.out.println("case 2: ");
        ArrayList<String> arr2 = new ArrayList<>(12);
        System.out.println("初始化后:"+getCap(arr2));
        for (int i = 0; i < 13; i++) {
            arr2.add("11");
        }
        System.out.println("add了13次后:"+getCap(arr2));
    }
case 1: 
初始化后:0
add 1次后:10
add 13次后:15
-----------------------------
case 2: 
初始化后:12
add了13次后:18

说明

  • case 1:
    • 无参构造,初始化为空数组
    • 添加一个元素后,触发扩容,默认为10
    • 添加11个元素后,超过10,触发扩容,新的容量=10+10/2=15
  • case 2:
    • 有参构造,直接初始化一个长度为12的数组
    • 添加第13个元素时,触发扩容,新的容量=12+12/2=18

再比较一下初始化容量与不初始化容量在性能的差异

    public static void check() {


        new Thread(() -> {
            long start1 = System.nanoTime();
            noPram();
            long end1 = System.nanoTime();
            System.out.println("noPram: "+(end1-start1));
        }, "1").start();

        new Thread(() -> {
            long start1 = System.nanoTime();
            hasPram();
            long end1 = System.nanoTime();
            System.out.println("hasPram: "+(end1-start1));
        }, "1").start();
    }

    public static void noPram() {
        ArrayList<String> strings = new ArrayList<>();
        for (int i = 0; i < 100000000; i++) {
            strings.add("111");
        }
    }

    public static void hasPram() {
        ArrayList<String> strings = new ArrayList<>(100000000);
        for (int i = 0; i < 100000000; i++) {
            strings.add("111");
        }
    }
hasPram: 679629900
noPram: 1600227000

性能差异在大数下还是比较明显的

流程图

画了个简单的流程图,带颜色部分为重点
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值