java集合框架源码解读之ArrayList

java集合框架源码解读之集合框架解读之ArrayList

源码解读

源码总体情况

  • ArrayList的底层维护了一个Object[] elementData动态(可进行扩容)数组
  • 实例一个ArrayList对象时,底层会根据ArrayList构造函数是否有参,而对elementData数组进行初始化不同的长度。无参时,数组elementData默认长度为10;有参时数组elementData长度为传入的参数。无论实例ArrayList有参还是无参,今后每当elementData数组要进行扩容时,都会按照当前数组的长扩大1.5倍。如默认情况下(长度为10)每次扩容扩容情况10–>15–>22–>33…;当构造器传入参数如2时,每次扩容情况为2–>3–>4–>6…。接下来就按照有参和无参两种情况进行讲解源码(其实两种情况大同小异)。
  1. 无参情况下

1.首先让我们先看看实例化一个无参ArrayList发生了什么

  • 当我们实例化一个无参ArrayList对象时
public class ArrayList_ {
  public static void main(String[] args) {
      //当构造器没有传入参数时,追进源码
      ArrayList<Integer> list = new ArrayList<>();
  }
}
  1. 现在我们追进源码的第一层
/*------------------源码进入部分-----------------------*/
public ArrayList() {
     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
 }
/*------------------相关字段解读--------------------------*/
transient Object[] elementData;//字段一
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//字段二
 ```
 源码解读:
 1.当我们进入ArrayList无参构造器时发现,ArrayList底层维护的elementData数组(也就是字段一)
   指向了一个默认初始化长度为0的数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA(也就是字段二)
 ```

2.现在我们往实例化的ArrayList添加一个元素看看又发生了什么

  • 追加一个元素

    public class ArrayList_ {
        public static void main(String[] args) {
            //当构造器没有传入参数时
            ArrayList<Integer> list = new ArrayList<>();
            list.add(1);
        }
    }
    
  1. 源码第一层
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);
        //size是当前数组中有效数据的长度,size+1这里是用来假设当size长度加一时会不会发生扩容
        elementData[size++] = e;//在不发生数组越界的情况下将数据e添加到elementData数组中
        return true;
    }
   ```
   
   ```
   源码解读:
   1.当我们进入add()函数时,会首先进入ensureCapacityInternal函数,该函数的主要作用是判断当加入当前数据e时,会不会发生数组越界,越界时就会将这个数组进行扩容之后再加入数据。
   ```

   2.进入ensureCapacityInternal函数

```java
private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
/*------------------相关函数(方法)解读--------------------------*/
 private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);//DEFAULT_CAPACITY是elementData默认容量,长度为10
        }
        return minCapacity;
    }
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//数组有效数据长度
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
源码解读:
1.当我们进入ensureCapacityInternal函数时,会进入一个需要传入一个整型参数的函数ensureExplicitCapacity
2.ensureExplicitCapacity需要的参数由calculateCapacity提供
3.下面就calculateCapacity和ensureExplicitCapacity进行解读
	3.1  calculateCapacity函数的作用是如果elementData为DEFAULTCAPACITY_EMPTY_ELEMENTDATA(换言之就是在实例			 ArrayList时是否传参),当为true时,会将数组长度设为默认值DEFAULT_CAPACITY=10,并返回。反之,返minCapacity,
		 也就是上一层传入的size+1;
	3.2  ensureExplicitCapacity是决定是否扩容的关键函数,当minCapacity - elementData.length > 0(即添加当前元素		  后,有效数据长度大于数组的长度)结果为真时,就会进行扩容。

3.数组进行扩容

  • 由于我们实例ArrayList时没有将没有传入参数,底层将elementData指向了一个空数组(也就是上文提到的DEFAULTCAPACITY_EMPTY_ELEMENTDATA),由于是第一次添加元素,而elementData此时又是一个空数组,所以会第一次会触发扩容机制,下面进入grow扩容函数。

        private void grow(int minCapacity) {
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            //这里是指扩容后的数组的长度,大小为原数组长度的1.5倍,这里oldCapacity >> 1等价于oldCapacity / 2
            if (newCapacity - minCapacity=0的情况, < 0)
                //这里是防止newCapacity=minCapacity=0的情况,因为当实例化ArrayList构造器没有传参,从而导致elementData指向空数组,elementData数组长度为0
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                //这里是指扩容后的数组长度达到最大长度MAX_ARRAY_SIZE=2147483639时(通常用不到),会进行更大的扩容机制hugeCapacity,
                //这里不再赘述
                newCapacity = hugeCapacity(minCapacity);
            elementData = Arrays.copyOf(elementData, newCapacity);//最后将原数组的内容拷贝到一个长度为newCapacity的数组中去,并将elementData指向这个数组。至此ArrayList首次扩容结束,结果逐层返回。
        }
    

2.有参情况下

无参和有参差别不大,主要区别在于两者实例时和进行扩容时有些许不同,这里讲述一下两者有区别的地方,其它的两者大致相同,这里不再赘述

1.首先让我们先看看实例化一个有参ArrayList发生了什么

public class ArrayList_ {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>(2);
        //这里会将ArrayList维护的数组elementData初始化为一个数组长度为2的数组,以后扩容的起始点便从2开始,往后增加数组长度的1.5倍
    }
}
/*------------------进入ArrayList有参构造函数中--------------------------*/
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);
        }
    }

源码解读:
	可以看到当进入ArrayList有参构造函数时,会根据传入的参数值initialCapacity分为三种情况
	当initialCapacity > 0时,elementData会被初始化一个长度为initialCapacity的数组;
	当initialCapacity = 0时,elementData会指向一个空object数组,也就是相当于上文无参时指向的那个空数组
    其他情况,则会抛出IllegalArgumentException异常。

2.有参ArrayList扩容

public class ArrayList_ {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>(2);
        list.add(1);
        list.add(2);
        list.add(3);//在此处将会发生扩容,从此处追进源码
    }
}
/*-----------------进入扩容函数grow-----------------------*/
  private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
源码解读:
	从此处看出有参和无参的使用的同一个扩容函数,唯一不同的时扩容的量的问题
	无参情况下,数组以默认值10为起始值进行扩容,以后每次扩容时,会按照当前数组长度的1.5倍进行扩容;
	有参情况下,数组会以传入的参数initialCapacity为起始值进行扩容,以后每次扩容时,也会按照当前数组长度的1.5倍进行扩容.
	可以看出两者的主要差别在于起始值的不同。
	例如现在的elementData.length为2。
通过ArrayList底层源码分析该实现类优缺点及其注意事项

优点:ArrayList底层维护的是一个动态数组,可以很方便的通过数组下标进行查找操作,便于对中小型且经常查找的数据进行存储。

缺点:由于数组的局限性,所以在对ArrayList进行删除时,会进行大量的移动操作,大大消耗了时间,因此在对数据进行经常性的删除操作时,不推荐使用ArrayList。

使用细节:由上述ArrayList构造方法及其底层原理可知,我们可以对ArrayList起始扩容量进行控制,这就需要我们结合自身数据量进行传入合适的参数。如假设我们想要每次增加1000个数据项(假设使用无参构造函数),可我们每次增加10个就要进行扩容,非常的占用时间和内存,这时我们就可以使用有参函数并传入>1000的参数,这样我们就没必要每次增加数据时都要扩容,大大节约了内存和时间。

写在最后:初学java,文章用来记录学习笔记,如有错误或不妥之处,欢迎大佬赐教,不喜轻喷。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值