ArrayList1.8源码解析,扩容机制

ArrayList JDK1.8源码解析

这也是第一次开始写博客,对ArrayList的源码进行阅读之后的一些小小心得,有不足的地方欢迎进行补充。
ArrayList 1.8和1.7的几乎无差别,底层的实现以及增长机制也没有太大的变化。

首先介绍一下ArrayList的底层实现,底层使用数组实现的,Java中还有一个集合的实现和ArrayList差别不大,Vector他们都是用数组实现存储的,唯一的区别就是,Vector的方法使用了Synchronized(线程安全)来修饰的,由于使用了Synchronized锁,所以在多线程的情况下会比ArrayList安全,但是在性能上要比ArrayList差一些,也可以使用Collections.synchronizedList()方法将ArrayList变成线程安全的集合。
下面就介绍一下ArrayList的源码:

1.类中的属性

在这里插 从源码就可以看出来 ArrayList底层是由数组实现的.入图片描述
从源码就可以看出来 ArrayList底层是由数组实现的.类的属性中核心的属性为elementData,类型为Object[],这个数组就是用来存放数据的数组,被标记为transient,也就意味着在序列化的时候,此字段是不会被序列化的

2.构造方法

2.1. ArrayList(int)型构造方法
在这里插入图片描述
指定elementData数组的大小,不允许初始化长度小于0,否则抛出异常

2.2 ArrayList()型构造方法
在这里插入图片描述

当未指定初始化大小时,会给elementData赋值为空数组。

2.3 ArrayList(Collection<? extends E>)型构造函数
在这里插入图片描述
当传递的参数为集合类型时,会把集合类型转化为数组类型(通过Connection.toArray()),并赋值给elementData

3.核心方法分析

3.1 add(E e)

在这里插入图片描述
在add函数我们发现还有其他的函数ensureCapacityInternal,此函数可以理解为确保elementData数组有合适的大小。ensureCapacityInternal的具体函数如下 
在这里插入图片描述
在ensureCapacityInternal函数中我们又发现了ensureExplicitCapacity函数,这个函数也是为了确保elemenData数组有合适的大小。ensureExplicitCapacity的具体函数如下
在这里插入图片描述

在ensureExplicitCapacity函数我们又发现了grow函数,grow函数才会对数组进行扩容,ensureCapacityInternal、ensureExplicitCapacity都只是过程,最后完成实际扩容操作还是得看grow函数,grow函数的具体函数如下  
在这里插入图片描述
在这里我们看到了有位移操作,可以把它当做乘以0.5,正常情况下会扩容1.5倍,特殊情况下(新扩展数组大小已经达到了最大值)则只取最大值。这里有一个Arrays.copyOf(),注意这里的拷贝是浅拷贝(这是一个本地方法,有c或c++编写的),底层调用的是System.arraycopy():
在这里插入图片描述
新数组中的第一个一直到结束
  当我们调用add方法时,实际上的函数调用如下

在这里插入图片描述
程序调用add,实际上还会进行一系列调用,可能会调用到grow,grow可能会调用hugeCapacity。
下面通过两种方式给出调用add的例子,并分析最后的elementData数组的大小。
在这里插入图片描述
初始化lists大小为0,调用的ArrayList()型构造函数,那么在调用lists.add(8)方法时,会经过怎样的步骤呢?下图给出了该程序执行过程和最初与最后的elementData的大小

我们可以看到,在add方法之前开始elementData = {};调用add方法时会继续调用,直至grow,最后elementData的大小变为10,之后再返回到add函数,把8放在elementData[0]中。
示例二核心代码如下
在这里插入图片描述

调用的ArrayList(int)型构造函数,那么elementData被初始化为大小为6的Object数组,在调用add(8)方法时,具体的步骤如下:
在这里插入图片描述
我们可以知道,在调用add方法之前,elementData的大小已经为6,之后再进行传递,不会进行扩容处理。

3.2set(int index,E element)

在这里插入图片描述
设定指定下标索引的元素值。

3.3indexOf(Object o)

在这里插入图片描述
 从头开始查找与指定元素相等的元素,注意,是可以查找null元素的,意味着ArrayList中可以存放null元素的。与此函数对应的lastIndexOf,表示从尾部开始查找。

3.4 get(int index)

在这里插入图片描述
这一点就可以看出ArrayList底层是数组(因为数组取值就是ary[index])
get函数会检查索引值是否合法(只检查是否大于size,而没有检查是否小于0),值得注意的是,在get函数中存在element函数,element函数用于返回具体的元素,具体函数如下 
在这里插入图片描述

3.5 remove(int index)

在这里插入图片描述
remove函数用户移除指定下标的元素,此时会把指定下标到数组末尾的元素向前移动一个单位,并且会把数组最后一个元素设置为null,这样是为了方便之后将整个数组不被使用时,会被GC,可以作为小的技巧使用。

4.扩容机制

ArrayList扩容的核心从ensureCapacityInternal方法说起。可以看到前面介绍成员变量的提到的ArrayList有两个默认的空数组:DEFAULTCAPACITY_EMPTY_ELEMENTDATA:是用来使用默认构造方法时候返回的空数组。如果第一次添加数据的话那么数组扩容长度为DEFAULT_CAPACITY=10。

EMPTY_ELEMENTDATA:出现在需要用到空数组的地方,其中一处就是使用自定义初始容量构造方法时候如果你指定初始容量为0的时候就会返回。
从下面可以看到如果是使用了空数组EMPTY_ELEMENTDATA话,那么不会返回默认的初始容量。
在这里插入图片描述
下面谈谈ensureExplicitCapacity方法(modCount设计到Java的快速报错机制后面会谈到),可以看到如果修改后的数组容量大于当前的数组长度那么就需要调用grow进行扩容,反之则不需要。
在这里插入图片描述
最后看下ArrayList扩容的核心方法grow(),下面将针对三种情况对该方法进行解析:
1.当前数组是由默认构造方法生成的空数组并且第一次添加数据。此时minCapacity等于默认的容量(10)那么根据下面逻辑可以看到最后数组的容量会从0扩容成10。而后的数组扩容才是按照当前容量的1.5倍进行扩容;
2.当前数组是由自定义初始容量构造方法创建并且指定初始容量为0。此时minCapacity等于1那么根据下面逻辑可以看到最后数组的容量会从0变成1。这边可以看到一个严重的问题,一旦我们执行了初始容量为0,那么根据下面的算法前四次扩容每次都 +1,在第5次添加数据进行扩容的时候才是按照当前容量的1.5倍进行扩容。
3.当扩容量(newCapacity)大于ArrayList数组定义的最大值后会调用hugeCapacity来进行判断。如果minCapacity已经大于Integer的最大值(溢出为负数)那么抛出OutOfMemoryError(内存溢出)否则的话根据与MAX_ARRAY_SIZE的比较情况确定是返回Integer最大值还是MAX_ARRAY_SIZE。这边也可以看到ArrayList允许的最大容量就是Integer的最大值(-2的31次方~2的31次方减1)。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值