ArrayList自动扩容解析

探索ArrayList自动改变size真相
ArrayList的列表对象实质上是存储在一个引用型数组里的,有人认为该数组有“自动增长机制”可以自动改变size大小。正式地说,该数组是无法改变
大小的,实际上它只是改变了该引用型数组的指向而已。下面,让我们来看看java是怎样实现ArrayList类的。
一、ArrayList类的实质
      ArrayList底层采用Object类型的数组实现,当使用不带参数的构造方法生成ArrayList对象时,
实际上会在底层生成一个长度为 10 的Object类型数组。
     首先,ArrayList定义了一个私有的未被序列化的数组elementData,用来存储ArrayList的对象列表(注意只定义未初始):
   private  transient  Object[] elementData;
    
     其次,以指定初始容量(Capacity)或把指定的Collection转换为引用型数组后实例化elementData数组;如果没有指定,则预置初始容量为 10 进行
实例化。把私有数组预先实例化,然后通过copyOf方法覆盖原数组,是实现自动改变ArrayList的大小(size)的关键。有人说ArrayList是复杂的数组,我
认为不如说ArrayList是关于数组的系统的方法组合。
  ArrayList的构造方法源码如下:
     // 用指定的初始容量构造一个空列表。
     public  ArrayList( int  initialCapacity) {
         super ();
         if  (initialCapacity <  0 )
             throw  new  IllegalArgumentException( "Illegal Capacity: " +initialCapacity);
         this .elementData =  new  Object[initialCapacity]; //属性指向新建长度为初始容量的临时数组
     }
     // 使用初始容量10构造一个空列表
     public  ArrayList() {
         this ( 10 );
     }
     / *构造包含利用collection的迭代器按顺序返回的指定collection元素的列表
      @param  c 集合,它的元素被用来放入列表t
      @throws  NullPointerException 如果指定集合为  null
      */
     public  ArrayList(Collection<?  extends  E> c) {
         elementData = c.toArray(); //用Collection初始化数组elementData
         size = elementData.length;
         if  (elementData.getClass() != Object[]. class )
             elementData = Arrays.copyOf(elementData, size, Object[]. class );
     }
二、ArrayList实现自动改变size机制
    为了实现这一机制,java引进了Capacity和size概念,以区别数组的length。为了保证用户增加新的列表对象,java设置了最小容量(minCapacity)
,通常情况上,它大于列表对象的数目,所以Capactiy虽然就是底层数组的长度(length),但是对于最终用户来讲,它是无意义的。而size存储着列表
对象的数量,才是最终用户所需要的。为了防止用户错误修改,这一属性被设置为privae的,不过可以通过size()获取。
    下面,对ArrayList的初始以及其列表对象的增加和删除等三种情况下的size自动改变机制进行分析。
    1 、初始Capacity和size值。
    从上面给出的ArrayList构造方法源码中,我们不难看出Capacity初始值(initialCapacity)可以由用户直接指定或由用户指定的Collection集合存
储的对象数目确定,如果没有指定,系统默认为 10 。而size的被声明为 int 型变量,默认为 0 ,当用户指定Collection创建ArrayList时,size值等于
initialCapacity。
 
    2 、add()方法
     该方法的源码如下:
     public  boolean  add(E e) {
         ensureCapacityInternal(size +  1 );
         elementData[size++] = e; //添加对象时,自增size
         return  true ;
     }
     方法中调用的ensureCapacityInternal主要用来调整容量,修改elementData数组的指向。其中涉及到 3 个方法的调用,其核心在于grow方法:
    
     private  void  ensureCapacityInternal( int  minCapacity) {
         modCount++; //定义于ArrayList的父类AbstractList,用于存储结构修改次数
         // overflow-conscious code
         if  (minCapacity - elementData.length >  0 )
             grow(minCapacity);
     }  
     private  void  grow( int  minCapacity) {
         // overflow-conscious code
         int  oldCapacity = elementData.length;
         int  newCapacity = oldCapacity + (oldCapacity >>  1 ); //新容量扩大到原容量的1.5倍,右移一位相关于原数值除以2。
         if  (newCapacity - minCapacity <  0 )
             newCapacity = minCapacity;
         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 :
             MAX_ARRAY_SIZE; //MAX_ARRAY_SIZE和Integer.MAX_VALUE为常量,详细请参阅下面的注解
     }
   
    通过以上代码,我们可知java自动增加ArrayList大小的思路是:向ArrayList添加对象时,原对象数目加 1 如果大于原底层数组长度,则以适当长度新
建一个原数组的拷贝,并修改原数组,指向这个新建数组。原数组自动抛弃(java垃圾回收机制会自动回收)。size则在向数组添加对象,自增 1
 
    注解:
     //定义于该类的常量,用来分配数组的size最大值。一些 VMs在数组里保留字头,试图分配更大数组时可能导致OutOfMemoryError:被请求数组的
size超出VM界限。
     private  static  final  int  MAX_ARRAY_SIZE = Integer.MAX_VALUE -  8 ;
    //在java.lang.Integer类中常量MIN_VALUE、MAX_VALUE如下:
    public  static  final  int    MIN_VALUE =  0x80000000 ; //整型取值区间下界:-2147483648
    public  static  final  int    MAX_VALUE =  0x7fffffff ; //整型取值区间上界:2147483647
   //在java.util.AbstractList中modCount定义如下:
   protected  transient  int  modCount =  0 ;
     3 、remove()方法
     该重构方法其一源码如下(其它的就不累述了):
     public  E remove( int  index) {
         rangeCheck(index);
         modCount++;
         E oldValue = elementData(index);
         int  numMoved = size - index -  1 ;
         if  (numMoved >  0 )
             System.arraycopy(elementData, index+ 1 , elementData, index,
                              numMoved); //将后面的列表对象前移
         elementData[--size] =  null // 数组前移一位,size自减,空出来的位置置null,具体的对象的销毁由Junk收集器负责
         return  oldValue;
     }
     private  void  rangeCheck( int  index) { //边界检查
         if  (index <  0  || index >=  this .size)
            throw  new  IndexOutOfBoundsException(outOfBoundsMsg(index));
     }
     E elementData( int  index) { //获取指定index所在位置的对象
         return  (E) elementData[index];
     }
 
     通过remove()源码的学习,我们不难看出,其改变ArrayList大小的核心与add()方法相似,都是同数组拷贝。
     另外,如果确有必要,用户也可以指定ArrayList实例的容量,可以有效的降低时间成本。它是通过调用ensureCapacityInternal来实现的,源代码
如下:
     public  void  ensureCapacity( int  minCapacity) {
         if  (minCapacity >  0 )
             ensureCapacityInternal(minCapacity);
     }
     因为size为 private 的,java给出方法来访问它:
     public  int  size() {
          checkForComodification();
          return  this .size;
     }
 
     综上所述,在用户向ArrayList追加对象时,Java总是要先计算容量(Capacity)是否适当,若容量不足则把原数组拷贝到以指定容量为长度创建的
新数组内,并对原数组变量重新赋值,指向新数组。在这同时,size进行自增 1 。在删除对象时,先使用拷贝方法把指定index后面的对象前移 1 位(如果

有的话),然后把空出来的位置置null,交给Junk收集器销毁,size自减1,即完成了




都知道ArrayList是基于数组的,那它是怎么实现可变的呢?


创建ArrayList对象时,ArrayList有个带参数的构造函数,那个参数的意思就代表着ArrayList长度,默认情况是10。当数据多了,ArrayList容不下时,这时ArrayList会增加长度,newLength = oldLength + oldLength/2;如果初始值是10,那么依次是15,22,33,49,73......,长度是增加了,那是怎样实现的呢?当数据容不下时,ArrayList会再创建一个更大的数组,数组长度为之前所说的那样,然后将之前的数据拷贝到新数组中。这就是ArrayList基于数组实现的可变长度原理。


下面详细解释下:


1、代码如下






ArrayList初始化时没有设置容量大小,随后给他添加10个字符串,size变为10;当10个字符串添加完之后,继续添加,这时ArrayList容量增加到15,size变为11。如果当ArrayList容量又满了时,这时ArrayList容量增加到22,以此类推(10,15,22,33,49......),newCapacity = oldCapacity+(oldCapacity/2),截图如下:





2、如果ArrayList初始化时不给其设置容量大小,当调用add方法时,会给其默认分配10容量值。



3、ArrayList中add方法

[java]  view plain  copy
  1. /** 
  2.     * Appends the specified element to the end of this list. 
  3.     * 
  4.     * @param e element to be appended to this list 
  5.     * @return <tt>true</tt> (as specified by {@link Collection#add}) 
  6.     */  
  7.    public boolean add(E e) {  
  8.        ensureCapacityInternal(size + 1);  // Increments modCount!!  
  9.        elementData[size++] = e;  
  10.        return true;  
  11.    }  
添加操作,首先会调用 ensureCapacityInternal(size + 1),其作用为保证数组的容量始终够用,其中 size是elementData数组中元组的个数,初始为0。


ArrayList中ensureCapacityInternal方法

[java]  view plain  copy
  1. private void ensureCapacityInternal(int minCapacity) {  
  2.        if (elementData == EMPTY_ELEMENTDATA) {  
  3.            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);  
  4.        }  
  5.   
  6.        ensureExplicitCapacity(minCapacity);  
  7.    }  
  8.   
  9.    private void ensureExplicitCapacity(int minCapacity) {  
  10.        modCount++;  
  11.   
  12.        // overflow-conscious code  
  13.        if (minCapacity - elementData.length > 0)  
  14.            grow(minCapacity);  
  15.    }  
ensureCapacityInternal()函数中,用if判断, 如果数组没有元素,给数组一个默认大小,会选择实例化时的值与默认大小中较大值,然后调用ensure Explicit Capacity()。



ArrayList中grow方法

[java]  view plain  copy
  1. /** 
  2.    * Increases the capacity to ensure that it can hold at least the 
  3.    * number of elements specified by the minimum capacity argument. 
  4.    * 
  5.    * @param minCapacity the desired minimum capacity 
  6.    */  
  7.   private void grow(int minCapacity) {  
  8.       // overflow-conscious code  
  9.       int oldCapacity = elementData.length;  
  10.       int newCapacity = oldCapacity + (oldCapacity >> 1);  
  11.       if (newCapacity - minCapacity < 0)  
  12.           newCapacity = minCapacity;  
  13.       if (newCapacity - MAX_ARRAY_SIZE > 0)  
  14.           newCapacity = hugeCapacity(minCapacity);  
  15.       // minCapacity is usually close to size, so this is a win:  
  16.       elementData = Arrays.copyOf(elementData, newCapacity);  
  17.   }  

函数体中,modCount是数组发生size更改的次数。然后if判断,如果数组长度小于默认的容量10,则调用扩大数组大小的方法grow()。

[java]  view plain  copy
  1. >>表示带符号右移,如:int i=15; i>>2的结果是3,移出的部分将被抛弃。  
  2. 转为二进制的形式可能更好理解,0000 1111(15)右移2位的结果是0000 0011(3),0001 1010(18)右移3位的结果是0000 0011(3)。  
10>>1 为5 


15>>1 为7


22>>1 为11 


4、从内部实现机制来讲ArrayList是使用数组(Array)来控制集合中的对象。当你增加元素的时候,如果元素的数目超出了内部数组目前的长度,它需要扩展内部数组的长度,ArrayList是原来的50%,即newCapacity = oldCapacity+(oldCapacity/2)



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值