ArraryList无参构造的扩容机制,附部分源码

1.简介

ArrayList是java集合当中的一个重要的类,它继承了AbstractList类,同时实现了List接口,是一个长度可变的集合。其实现了RandomAccess,Serializable,Cloneable接口,所ArrayList 是支持快速访问、序列化、复制的,注意,由于ArrayList的所有方法都没有加关键字synchronized,因此ArrayList是线程不安全的。

其继承体系结构图如下(图来自于idea)

2.ArrayList的底层部分字段概念

我们将解析ArrayList的构造方法和扩容机制,在看构造方法之前,我们先来明确一下ArrayList源码中的一些概念。这些变量和对象大家可能有疑惑,先记住就好了,后面会看到它们的用途,这些看懂了对理解源码有很大的帮助。

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {

    private static final long serialVersionUID = 8683452581122892189L;
    //这是一个序列号版本uid,不用管它

    private static final int DEFAULT_CAPACITY = 10;
    //不指定大小时创建ArrayList时初始的大小,注意当我们直接创造ArryList时,默然是一个大小为0的空 
    //数组,只有当我们向其添加元素时,才会触发扩容机制,将这个大小赋给ArrayList。

    private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
    //定义的一个空数组,大小固定为0,不可变。不可变的原因是final关键字。

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
    //定义的默认空容量的数组,依然不可变。

    transient Object[] elementData;
    //这里才是真正的存储元素的数组,其类型是object,这就是为什么ArrayList能够放任何类型数据的根 
    //本原因

    private int size;
    //数组当中元素的个数,默认为0。默认为0是因为int如果不赋初值则默认为0.

    private static final int MAX_ARRAY_SIZE = 2147483639;
    //数组能存储的最大元素个数
}

ArrayList的构造方法

 

ArrayList有三种构造方法,分别是无参、有参和通过传入Collection元素列表进行生成。这里主要讲解其扩容机制,所以选择无参构造演示。

 public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        //this.elementData:前面提到的真正存放元素的object数组
        //DEFAULTCAPACITY_EMPTY_ELEMENTDATA:默认空数组
    }

     可以看出来当我们创建一个ArrayList时,其elementData这个真正存放元素的数组被赋予了默认空容量的数组。

ArrayList的扩容机制


ArrayList的扩容机制是比较简单的,我们这里以一次无参构造作为示例,通过它的一次add来分析。

      由上文知道,我们创建出来的数组是一个空容量的数组,因此当我们向其添加一个元素时,必然会触发其扩容机制,我们通过以下简单代码进行测试。

public class ArrayList_ {
   public static void main(String[] args) {
         ArrayList arrayList=new ArrayList();
         //通过无参创建一个默认初始容量为0的空数组
         arrayList.add("测试");
         //向这个空数组添加一个元素
   }

      当执行add时,毫无疑问调用add这个函数,这里的modCount是其父类AbstractList<E>当中的一个属性,用来记录对数组修改的次数,不重要。

  public boolean add(E e) {
        ++this.modCount;   
        //记录我们对这个数组的修改次数,不重要
        this.add(e, this.elementData, this.size);  
        //真正执行添加的函数,其中e为我们传经来的数据,this.elementData是无参构造时得到的初始容            
        //量为空的object数组
        return true; 
    }

     在add这个函数内部又调用了一个add,注意这两个add函数不是一个函数,真正执行添加的是this.add(E e, Object[] elementData, int s)这个函数。

 private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length) {   //判断元素个数是否等于当前数组容量。
            elementData = this.grow();
        }
        elementData[s] = e;  
        this.size = s + 1;
    }

     首先,它判断了元素个数是否等于当前数组的容量,也就是判断当前数组是不是满的,如果当前空间是满的,就需要扩容了,grow函数就是扩容函数了,扩容后再将被加的元素加到数组中。当然在这里肯定是相等的,那么必然要进入grow函数了,今天的重头戏来了

 private Object[] grow() {
        return this.grow(this.size + 1);
    }

     可以看到在里面它又调用了一个grow函数,真正实现的扩容函数,依然是现在马上要执行的grow函数,看看它具体长什么样,

 private Object[] grow(int minCapacity) {  //当前需要的最小容量
       return this.elementData = Arrays.copyOf(this.elementData,            
        this.newCapacity(minCapacity));
        //在这个grow函数当中,进行扩容机制的是this.newCapacity(minCapacity)
    }

     在这个grow函数当中它执行Arrays.copyOf(this.elementData, this.newCapacity(minCapacity))这个函数(不懂这个函数的可以去查查哦,这里就不细讲),在将newCapacity(minCapacity)执行后的新的数组赋给elementData,现在重头戏来了,我们来研究这个真正执行扩容的newCapacit(minCapacity)函数

 private int newCapacity(int minCapacity) {   //需要的最小容量
        int oldCapacity = this.elementData.length;  
        //定义一个oldCapacity存储现在的elementData数组元素的长度

        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //定义一个newCapacity存储扩容后的新数组长度,将原先的数组长度进行右移,然后再加上原先的 
        //数组长度,相当于加上原先的一半。

        if (newCapacity - minCapacity <= 0) { //判断当前新的数组长度是否满足需要的最小容量
            if (this.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //重点:如果当前的数组为空数组,那么就执行以下语句,取Math.max(10,minCapacity)当中大的        
        //一个返回
                return Math.max(10, minCapacity);
            } else if (minCapacity < 0) {
                throw new OutOfMemoryError();
            } else {
                return minCapacity;
            }
        } else {
            return newCapacity - 2147483639 <= 0 ? newCapacity : hugeCapacity(minCapacity);
        }
    }

     由于当前我们的elementData数组是一个空数组,那oldCapacity=0,newCapacity=0+0*0.5=0,必然会进入当前语句

 if (newCapacity - minCapacity <= 0) {
            if (this.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                return Math.max(10, minCapacity);
            } else if (minCapacity < 0) {
                throw new OutOfMemoryError();
            } else {
                return minCapacity;
            }
        }

     同时由于elementData数组是一个空数组,那么返回的一个较大的数

private Object[] grow(int minCapacity) {
        return this.elementData = Arrays.copyOf(this.elementData,                 
        this.newCapacity(minCapacity));
        //这里的this.newCapacity(minCapacity))就得到一个10,接着执行Arrays.copyof()函数,进行 
        //对elementData扩容/
    }

     然后elementData就从原先的0大小,变为了10的大小,扩容完成。逐步返回原先的add函数

 private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length) {
            elementData = this.grow();
        }

        elementData[s] = e; 
        //if语句执行完成,现在我们的elementData有10个空间大小,将第一个即下标为0的数组指向e
        this.size = s + 1; 
        //数组大小变为1
    }

进行赋值,然后当前元素就加入了ArrayList集合,分析完成。

总结

ArrayList的扩容机制是比较简单的,能理解其无参构造下的扩容机制,对其各种情况下的扩容机制应该也不会有太大的难度,希望能对初学者有一定的帮助。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值