ArrayList 底层源码如何动态扩容流程详解?

ArrayList

在这里插入图片描述

doc

  • 列表接口的可调整大小的数组实现。实现所有可选的列表操作,并允许所有元素,包括null。除了实现List接口之外,这个类还提供了一些方法来操作内部用于存储列表的数组的大小。(这个类大致相当于Vector,只是它是不同步的。线程不安全)
  • size、isEmpty、get、set、iterator和listIterator操作在固定时间内运行。add操作以摊余常量时间运行,即添加n个元素需要O(n)时间。所有其他操作都是在线性时间内运行的(粗略地说)。与LinkedList实现相比,常量因子较低。
  • 每个ArrayList实例都有一个容量。容量是用于存储列表中元素的数组的大小。它总是至少和列表大小一样大。当元素被添加到ArrayList时,它的容量会自动增长。除了增加一个元素具有固定的摊余时间成本这一事实外,增长策略的细节没有被指定。
  • 应用程序可以在使用ensureCapacity操作添加大量元素之前增加ArrayList实例的容量。这可能会减少增量重新分配的数量。(创建ArrayList集合可以初始自己的容量
  • 请注意,此实现不是同步的。如果多个线程同时访问一个ArrayList实例,并且至少有一个线程在结构上修改了列表,那么它必须在外部同步。(结构修改是指添加或删除一个或多个元素,或显式调整后备数组大小的任何操作;仅仅设置元素的值不是结构修改。)这通常是通过在自然封装列表的某个对象上同步来完成的。如果不存在这样的对象,则应该使用集合.synchronizedList方法。最好在创建时执行此操作,以防止意外的未同步访问列表:
List list = Collections.synchronizedList(new ArrayList(...));
  • 这个类的iterator和listIterator方法返回的迭代器是快速失败的:如果在迭代器创建之后的任何时候,以任何方式(除了通过迭代器自己的remove或add方法)**对列表进行结构修改,迭代器将抛出一个ConcurrentModificationException。(意思就是说ArrayList不适合修改)**因此,在并发修改的情况下,迭代器会迅速而彻底地失败,而不是在将来某个不确定的时间冒着任意的、不确定的行为的风险。

构造方法属性

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * 默认初始容量。
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 用于空实例的共享空数组实例。
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
    用于默认大小的空实例的共享空数组实例。我们将其与空的元素数据区分开来,
    以了解添加第一个元素时要扩容多少。
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     存储ArrayList元素的数组缓冲区。ArrayList的容量是这个数组缓冲区的长度。
     添加第一个元素时,任何elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList
     都将扩展为默认的DEFAULT_CAPACITY(默认长度10)。
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * ArrayList的大小(它包含的元素数)。
     */
    private int size;

    /**
     *构造具有指定初始容量的空列表。
     *
     * @param  列表的初始容量
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    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);
        }
    }

    /**
     * 构造一个初始容量为10的空列表
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
 /**
     构造一个包含指定集合元素的列表,其顺序由集合的iterate返回
      (collection接口规范 实现改接口或子接口的实现类(继承不算)都得编写一个构造方法参数为Collection和一个无参构造方法)
     */
    public ArrayList(Collection<? extends E> c) {
    	//集合的数组
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray 可能(错误地)不返回Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
            	//copy数组
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 空实例数组此时会默认初始容量为10
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

动态扩容流程

1.确定内部容量

//该方法重写AbstractList的add方法
 public boolean add(E e) {
  		 //确保内部容量(通过判断,如果够则不进行操作;容量不够就扩容来确保内部容量)
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e; //此时将元素依次赋值
        return true;
    }

ensureCapacityInternal方法名的英文大致是“确保内部容量”,size表示的是执行添加之前的元素个数(我们构造时已经将其赋值),并非ArrayList的容量,容量应该是数组elementData的长度。ensureCapacityInternal该方法通过将现有的元素个数数组的容量比较。看如果需要扩容,则扩容。

 private static int calculateCapacity(Object[] elementData, int minCapacity) {
		 //如果实际存储数组 是空数组,则最小需要容量就是默认容量
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

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

        // overflow-conscious code
        //如果 最小容量大于当前数组的长度 就需要扩容
        if (minCapacity - elementData.length > 0)
        	//扩容
            grow(minCapacity);
    }

以上,elementData是用来存储实际内容的数组。minExpand 是最小扩充容量
DEFAULTCAPACITY_EMPTY_ELEMENTDATA共享的空数组实例用于默认大小的空实例。根据传入的最小需要容量minCapacity来和数组的容量长度对比,若minCapactity大于或等于数组容量,则需要进行扩容。

2.扩容

 /**
   要分配的数组的最大大小。一些vm在数组中保留一些头字。
   尝试分配较大的阵列可能会导致OutOfMemoryError:请求的阵列大小超过VM限制
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
	   增加容量,以确保它至少可以容纳minimum capacity参数指定的元素数。
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        //旧长度
        int oldCapacity = elementData.length;
          //>> 把>>左边的数据除以2的移动次幂 10/2^1 = 5
         //>>位运算,右移动一位。 整体相当于newCapacity =oldCapacity + 0.5 * oldCapacity  
        // jdk1.7采用位运算比以前的计算方式更快 
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
            //判断最大个数 意思就是说你扩容数量不能大于int的存储最大值
        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);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE ://int的最大值 
            MAX_ARRAY_SIZE;
    }

综上所述:ArrayList相当于在没指定initialCapacity时就是会使用延迟分配对象数组空间,当第一次插入元素时才分配10(默认)个对象空间。假如有20个数据需要添加,那么会分别在第一次的时候,将ArrayList的容量变为10 (如下图一);之后扩容会按照1.5倍增长。也就是当添加第11个数据的时候,Arraylist继续扩容变为10*1.5=15(如下图二);当添加第16个数据时,继续扩容变为15 * 1.5 =22个
  向数组中添加第一个元素时,新生产数组容量为10.

在这里插入图片描述

向数组中添加到第10个元素时,数组容量仍为10.
在这里插入图片描述

向数组中添加到第11个元素时,数组容量扩为15.
在这里插入图片描述

向数组中添加到第16个元素时,数组容量扩为22.
在这里插入图片描述

每次扩容都是通过Arrays.copyOf(elementData, newCapacity) 这样的方式实现的。
总结:当我们在使用无参构造方法在构造ArrayList时,会将this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA 赋值,此时由于elementData属性规定当为该属性时
会默认使用初始值容量10。当容量大于初始容量时开始扩容 每次按照1.5倍(位运算)的比率通过copeOf的方式扩容
以上就是动态扩容的原理。

remove删除指定元素

/**
   删除此列表中指定位置的元素。向左移动任何后续元素(从其索引中减去一个)
     *
     * @param 要删除的元素下标
     * @return 从列表中删除的元素
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
    	//判断下标是不是超出size
        rangeCheck(index);
		//该数据结构被修改的次数
        modCount++;
        //取出被删除的值
        E oldValue = elementData(index);
		//获取新数组长度
        int numMoved = size - index - 1;
        if (numMoved > 0)
        	//copy
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
         //数组长度减一
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
     /**
    从列表中删除第一个出现的指定元素(如果存在)。如果列表中不包含元素,则它将保持不变。更正式地说,删除索引i最低的元素,这样(o==null?get(i)==null:o.equals(get(i))(如果存在这样的元素)。如果此列表包含指定的元素,则返回true(如果此列表因调用而更改,则返回等效值)。
     *
     * @param o 要从此列表中删除的元素(如果存在)
     * @return <tt>true</tt> 如果此列表包含指定的元素
     */
        public boolean remove(Object o) {
        //为null
        if (o == null) {
            for (int index = 0; index < size; index++)
            //删除null
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
            .	//一个个比较删除第一个
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
/*
私有remove方法,它跳过边界检查并且不返回已删除的值。
*/
 private void fastRemove(int index) {
 //数据结构修改数++
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

如上:可以看出对于 ArrayList 元素的删除操作是分为两种:1.通过下标删除底层会线盘多是否下标越界 无越界删除指定下标数据通过System.arraycopy将新的数据重新copy到当前数组当中;2.通过值删除先判断是否为null删除第一个为null的数据如果没有删除该值出现的第一个数据调System.arraycopy方法。,需要将被删除元素的后续元素向前移动,代价比较高

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值