Java Collection框架(三)ArrayList原理浅析

​Java Collection框架 ArrayList源码解读 与 实现 原理浅析

2018拍摄于京都智积院

微信公众号

 

王皓的GitHub:https://github.com/TenaciousDWang

 

java.util.List

 

 

List集合是线性数据结构的主要实现。List本身是Collection接口的子接口,具备了Collection的所有方法。

 

List有三个主要实现:

 

ArrayList:底层的数据结构是数组,非线程安全,ArrayList替代了Vector,查询元素的速度非常快。默认大小10,每次扩容1.5倍。

 

LinkedList:底层的数据结构是链表,非线程安全,增删元素的速度非常快。

 

Vector:底层的数据结构就是数组,线程安全,Vector无论查询和增删都巨慢。默认大小10,2倍长度扩容(已经基本弃用)。

 

本篇我们来看一下ArrayList的源码和实现原理。

 

 

位于package java.util; 继承了AbstractList并实现了List接口。

 

主要成员变量

 

 

主要成员变量:

 

serialVersionUID,序列化版本号。

 

DEFAULT_CAPACITY,默认初始化容量,为10,如果没有使用显式容量创建ArrayList,那么ArrayList底层数组初始化容量为10.

 

EMPTY_ELEMENTDATA,如果在创建ArrayList时指定容量为0时,ArrayList内部会将这个空数据赋给elementData。

 

DEFAULTCAPACITY_EMPTY_ELEMENTDATA,如果没有用显式容量创建ArrayList,ArrayList内部会将这个空数据赋给elementData。

 

elementData,ArrayList的底层数据结构,一个对象数组,用于存放实际元素,并且标记transient关键字,在序列化的时候此字段是不会被序列化的。

 

size,ArrayList中包含元素的数量,默认为0。

 

 

MAX_ARRAY_SIZE,数组最大容量为Integer.MAX_VALUE - 8。

 

构造器

 

接下来我们看一下ArrayList的构造函数,就可以更好的理解上面的成员变量。

 

 

第一个为显式容量构造函数,在创建ArrayList时指定初始化容量,当initialCapacity>0时,创建一个数组并指定数组容量为initialCapacity,赋值给elementData。

 

如果initialCapacity==0时,将成员变量EMPTY_ELEMENTDATA这个空数组赋值给elementData。

 

如果initialCapacity<0时,会抛出IllegalArgumentException异常,非法容量。

 

 

无参数构造函数直接将成员变量DEFAULTCAPACITY_EMPTY_ELEMENTDATA(空数组)赋值给elementData。

 

 

第三个构造函数是将入参结合转化为数组,赋值给ArrayList的底层数组elementData,然后将elementData数组长度赋值给size,并判断是否不等于0,且判断elementData.getClass不等于Object[].class,使用数组的copyOf方法重新给elementData赋值,c.toArray 可能 (不正确) 不返回Object[]。如果size等于0,则将空数组EMPTY_ELEMENTDATA赋值给elementData。

 

public boolean add(E e)

 

下面我来分析一下ArrayList的add方法,看一下ArrayList是如何实现数组结构扩容的,ArrayList核心思想的体现。

 

 

当我们调用add(E e)时,首先执行ensureCapacityInternal(size + 1);进行增量。

 

 

ensureCapacityInternal内首先调用calculateCapacity进行比较,如果elementData为空数组,则使用默认数组容量大小10与size+1进行比较,返回大的,如果elementData不为空,则直接返回size+1.

 

 

接下来ensureExplicitCapacity判断size+1减去当前elementData数组长度是否大于0,如果大于0则开始执行grow方法。

 

 

增加容量,以确保它至少可以容纳由最小容量参数(size+1)指定的元素数目。

 

int oldCapacity = elementData.length;首先给oldCapacity赋值当前elementData的长度,然后用oldCapacity>>1带符号右移(除以2)加上oldCapacity为原数组elementData长度1.5倍。

 

最后判断1.5倍长度减去minCapacity(size+1)如果小于0则将minCapacity当做新的扩容大小,如果newCapacity大于MAX_ARRAY_SIZE(数组最大容量)则执行hugeCapacity操作。

 

 

1.5倍扩容后容量超过MAX_ARRAY_SIZE都返回Integer.MAX_VALUE,小于则继续按照MAX_ARRAY_SIZE当做当前elementData的容量即ArrayList容量,如此往复直到minCapacity < 0,抛出OutOfMemoryError(OOM异常)。

 

elementData = Arrays.copyOf(elementData, newCapacity);复制指定的数组,截断或用空填充(如有必要),以便复制具有指定的长度。

 

到此,ArrayList内部数组结构扩容完毕。

 

最后执行赋值操作elementData[size++] = e;返回true,return true;

 

在这里我们需要思考一下,当我们使用无参数构造一个ArrayList时,初始化数组大小为10,后续扩容每次都会调用Arrays.copyOf方法,反复复制数组创建数组,假设需要将1000个元素存入ArrayList中时,如果采用无参数构造,至少需要扩容13次,如果开始就指定容量直接分配1000容量的数组,可以避免扩容和数组复制的额外开销,但是如果初始化容量过大,也会由于数组体积过大导致性能消耗,且容易造成OOM风险,所以实际生产中,我们要根据实际情况来评估初始化容器的大小,尽可能的减少损耗和风险。

 

public void add(int index, E element)

 

接下来看一下add的一个重载方法,在指定index插入元素。

 

 

首先调用rangeCheckForAdd方法校验index的合法性。

 

 

接下来执行ensureCapacityInternal扩容。

 

然后使用System.arraycopy(elementData, index, elementData, index + 1,size - index);复制数组。

 

arraycopy() 方法位于 java.lang.System 类中,其语法形式如下

 

 

System.arraycopy(dataType[] srcArray,int srcIndex,int destArray,int destIndex,int length)

 

其中,srcArray 表示源数组;srcIndex 表示源数组中的起始索引;destArray 表示目标数组;destIndex 表示目标数组中的起始索引;length 表示要复制的数组长度。

使用此方法复制数组时,length+srcIndex 必须小于等于 srcArray.length,同时 length+destIndex 必须小于等于 destArray.length。

 

然后赋值elementData[index] = element; 尺寸size++。

 

public void ensureCapacity(int minCapacity)

 

 

这里有一个与扩容相关的方法一起看看,用来直接指定容量大小,只有当指定容量大于默认大小时才会执行ensureExplicitCapacity方法去直接扩容。

 

public void trimToSize()

 

 

这里是一个修剪尺寸的操作修剪List的容量至size大小,其实就是给elementData赋值一个复制的指定size容量的数组。只有当size小于ArrayList中Object[]的大小时才会设置。用来释放ArrayList未使用的内存。

 

public E set(int index, E element)

 

接下来看一下我们常用的set方法。

 

 

首先检查index是否合法,然后取根据index取出旧元素返回,新元素赋值给数组原index指针位置。

 

public int indexOf(Object o)

 

 

根据元素返回该元素在ArrayList集合中的指针位置。实际上是数组循环遍历查找。

 

public E get(int index)

 

 

最常用的get元素实际上就是返回elementData底层数组中指针index对应的元素。

 

public E remove(int index)

 

 

remove方法删除一个元素,然后将该元素后的所有元素向前移动一个位置。

 

以上就是ArrayList的实现原理浅析,除了上述主要使用的方法外还有很多别的方法,大家感兴趣也可以自行查看源码。下一篇我们来分析LinkedList的实现原理。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值