【Java容器】ArrayList扩容机制及源码分析


一、ArrayList介绍:

ArrayList 的底层是数组队列(Object[]),相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。

ArrayList 不保证线程安全;

在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。

构造函数:

    /**
     *默认无参构造函数
     */
    public ArrayList() {
    }
     /**
     * 带初始容量参数的构造函数     */
	public ArrayList(int initialCapacity) {
    }
    /**
     * 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
     */
    public ArrayList(Collection<? extends E> c) {
    }


二、ArrayList源码分析:

2.1 AL的父亲们:


public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  • RandomAccess 是一个标志接口(在某些排序和搜索算法中会检查是否是继承这个标志接口),表明实现这个这个接口的 List 集合是支持快速随机访问的。
  • ArrayList 实现了 Cloneable 接口 ,即覆盖了函数clone(),能被克隆。
  • ArrayList 实现了 java.io.Serializable 接口,这意味着ArrayList支持序列化,能通过序列化去传输。

2.2 AL的初始化,你不用就给你个空的:

	// 初始大小
	private static final int DEFAULT_CAPACITY = 10;
	// 初始空数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
	// ArrayList的底层实现数组
    transient Object[] elementData;
    /**
    	无参数构造函数,可见如果我们没有给定初始值,AL只是创建了一个空数组“糊弄人”
    */
        public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

通过上面我们看到了ArrayList无参初始化时并没有给定数组容量,其实这里就牵扯出了ArrayList的扩容机制:


三、ArrayList扩容分析:

3.1 add()方法,你用我我才给你开辟空间:

add()方法:(这里是我简化合并后的核心代码)

	// AL 的元素个数
    private int size;
    /**
     * 将指定的元素追加到此列表的末尾。
     */
    public boolean add(E e) {
    	int minCapacity = size + 1// 如果是默认初始化的数组
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
              // 获取默认的容量和size + 1的较大值,+1是因为add发生时正在存入第一个元素
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        // 如果add会发生内存溢出的情况,第一次add时便会最终进入grow扩容
        if (minCapacity - elementData.length > 0){
        	//调用grow方法进行扩容
            grow(minCapacity);
        }
        // 这里看到ArrayList添加元素的实质就相当于为数组赋值
        elementData[size++] = e;
        return true;
    }

分析一波:
其实add()方法最核心的判断就在于if (minCapacity - elementData.length > 0),我再翻译一下就是if (size + 1 - elementData.length > 0),也就是判断这次add是否会超过这时AL的容量,如果超过,那么就进入grow()方法进行扩容,第一次add()方法执行时,因为minCapacity=10 而elementData.length=0所以也会进grow()方法。


3.2 grow() :真正的扩容方法是我:

grow()方法:(同样是简化版本)

	// 要分配的最大数组大小
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
    private void grow(int minCapacity) {
        // 旧容量
        int oldCapacity = elementData.length;
        // oldCapacity /2,新容量实际是 oldCapacity * 1.5 
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 然后*1.5都不够的话,就要多少给多少,直接等于minCapacity
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 如果新容量大于了AL定义的最大容量,那就交给minCapacity来确定一个合适容量
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = minCapacity > MAX_ARRAY_SIZE) ?
            	Integer.MAX_VALUE :
            	MAX_ARRAY_SIZE;
        // 复制扩容操作
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

至于最后的copyOf方法内部实际调用了 System.arraycopy() 方法来实现数组的复制,通过复制来扩容。

这是总结:

无参初始容量零,一次add变成10,
1.5倍不够爽,最小容量来确定,
虽然数组有上限,动态数组还是爽

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值