面试常见问题:ArrayList 底层源码如何动态扩容流程详解?

   日期:2020-07-07     浏览:86    评论:0    
核心提示:ArrayListdoc列表接口的可调整大小的数组实现。实现所有可选的列表操作,并允许所有元素,包括null。除了实现List接口之外,这个类还提供了一些方法来操作内部用于存储列表的数组的大小。(这个类大致相当于Vector,只是它是不同步的。线程不安全)size、isEmpty、get、set、iterator和listIterator操作在固定时间内运行。add操作以摊余常量时间运行,即添加n个元素需要O(n)时间。所有其他操作都是在线性时间内运行的(粗略地说)。与LinkedList实现相

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 = {};

    
    transient Object[] elementData; // non-private to simplify nested class access

    
    private int size;

    
    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) {
    	//集合的数组
        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.扩容

 
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    
    private void grow(int minCapacity) {
        // overflow-conscious code
        //旧长度
        int oldCapacity = elementData.length;
         //>>位运算,右移动一位。 整体相当于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的方式扩容
以上就是动态扩容的原理。

删除指定元素


    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;
    }
     
        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;
    }

 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 元素的删除操作,需要将被删除元素的后续元素向前移动,代
价比较高

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服