jdk8下ArrayList源码分析实现原理

ArrayList
继承抽象类AbstractList
实现接口RandomAccess、Cloneable、Serializable
在这里插入图片描述
1.ArrayList的初始化
ArrayList的构造函数有3个,通过new出这三个构造函数即可得到ArrayList实例
在这里插入图片描述
那么这三个构造函数初数化的时候都做了什么操作呢?有什么区别呢?

先看看无参构造函数源码
在这里插入图片描述
其中有两个属性:elementData和DEFAULTCAPACITY_EMPTY_ELEMENTDATA,来看看定义:
在这里插入图片描述
可以看到,elementData是一个Object[]的数组,被transient修饰,告诉我们该对象在序列化的时候请忽略这个元素,这个属性是用来存储数据的,没错,ArrayList的数据是通过Object[]来存储的。
在这里插入图片描述
DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个长度为0的初始化过的Object[]数组,被static和final修饰,表明属性在类加载的时候就已经初始化了,只有一份,所有实例都共享此属性,并且不能被修改。

所以当调用无参的构造函数时,如:
在这里插入图片描述

获得ArrayList实例,并初数化elementData为一个长度为0的Object[]数组,严格来说,是将DEFAULTCAPACITY_EMPTY_ELEMENTDATA对象的引用指向elementData,内存结构图大概如下:
在这里插入图片描述
如果调用无参构造函数实例化多个ArrayList
在这里插入图片描述
内存结构图如下
在这里插入图片描述
此时会有人提出疑问,数组的长度大小是固定的,但是ArrayList添加的数据个数并不是固定的,多个实例共享此属性,其中一个实例修改其他实例的值都会改变,而且被final修饰,那么怎么添加数据呢?

2.ArrayList的add()
这时就要看看add()方法了
在这里插入图片描述
可以看到add方法有四种,先看看第一种
在这里插入图片描述

Person代码:
在这里插入图片描述

源码如下:
在这里插入图片描述
看看方法ensureCapacityInternal(size + 1); 干了什么,先看看size定义
在这里插入图片描述
size为基本数据类型,默认初始值为0,代表当前ArrayList的实例存储的数据个数,+1既代表下一个数据要存储的位置。那么一个实例第一次调用add的时候,调用ensureCapacityInternal(size + 1),参数就是1了,进入ensureCapacityInternal方法:
注意以下数据个数数组容量的区别,数组容量总是>=数据个数
在这里插入图片描述

首先判断elementData的引用是不是指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA指向的对象,如果是第一次调用add方法,是成立的,会进入if执行,第二次调用add就不成立了,为什么呢,往后看:
先看看DEFAULT_CAPACITY的定义:
在这里插入图片描述
可以看到注释的意思为默认初始化容量,没错,就是ArrayList里面elementData代表的Object[]数组的初始化大小,具体实现看ensureExplicitCapacity(minCapacity)方法,因为调用了Math.max(DEFAULT_CAPACITY, minCapacity),所以minCapacity的值为10
在这里插入图片描述

重头戏来了,看看grow(minCapacity)方法:
在这里插入图片描述

if(minCapacity - elementData.length > 0):如果当前数据存储的位置大于数组原有的长度,既调用grow()方法,通过拷贝进行数组容量扩容

看看数组的最大容量MAX_ARRAY_SIZE的值
在这里插入图片描述
ArrayList的最大容量为Integer.MAX_VALUE - 8

看看工具类Arrays的copyOf(elementData, newCapacity)的方法
在这里插入图片描述
调用了另外一个重载方法copyOf(original, newLength, original.getClass())
在这里插入图片描述
最终调用了System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength))方法
在这里插入图片描述
此方法为一个native方法,也就是说实现不是用java语言写的
总结:Arrays的两个copyOf()是用来复制数组的,
底层调用的System.arraycopy(Object src, int srcPos,Object dest, int destPos, int length)
System.arraycopy()也是用来复制拷贝数组的,参数如下
src:原数组,即被拷贝的数组
srcPos:从原数组的哪个下标开始拷贝
dset:目标数组,即拷贝到哪个数组上
destPos:以目标数组的哪个下标开始接收拷贝
length:拷贝多少个元素

总体来说,就是将elementData的Object[]数组重新拷贝一份,并扩容,长度为grow()方法里面计算的newCapacity的值(大概每次扩容的容量为以前容量的1.5倍),然后将elementData的指针指向新的被拷贝的数组。从此elementData和全局实例共享的不能被修改的DEFAULTCAPACITY_EMPTY_ELEMENTDATA指向的Object[]数组在没有关系

此时还没有进行存储操作,如果是第一次add,只是新建了一个容量为10的ArrayList实例,内存结构:
在这里插入图片描述

如果不是第一次操作,那么就是扩容后的ArrayList实例(大小大概为以前的1.5倍,之前的数据被复制进去了),新值存在size的位置,然后size的大小增加1
在这里插入图片描述
size的大小总是和ArrayList里面数据的个数相等,list1.size()获取的就是此size的值
在这里插入图片描述
添加数据后(第一次)内存结构:
在这里插入图片描述
4.ArrayList的其他构造函数
第二个构造函数可以指定初始化时,ArrayList的大小,即内部Object[] elementData数组初始化大小
在这里插入图片描述
第三个构造函数可以传入一个集合,然后根据集合的内容构建ArrayList的实例
在这里插入图片描述
其他add方法
第二个add(int,E)方法:将元素添加到指定下标处。
在这里插入图片描述
来看看rangeCheckForAdd(index);方法
在这里插入图片描述
注意这里的元素个数:如果一个新ArrayList(默认长度为10)只添加了一个元素,而index为2,哪怕还剩9个空容量,也会抛异常
,
执行以下代码的原理:
在这里插入图片描述
原理图:
在这里插入图片描述
第三个addAll(Collection<? extends E>)方法可以添加其他集合到末尾
在这里插入图片描述

第四个addAll(int, Collection<? extends E>)可以添加其他集合到指定的位置
在这里插入图片描述

如下代码:
在这里插入图片描述
if (numMoved > 0)代表不是从末尾开始添加,进行第一次拷贝,操作的是同一个数组
也就是list1的emementData在这里插入图片描述
然后第二次拷贝,操作的是被转换为Object[]的list2和list1的emementData
在这里插入图片描述
如果(numMoved = size - index)=0既标识向末尾添加,直接进行后面的拷贝,将list2的数据拷贝到list1的末尾。numMoved是不可能小于0的
在这里插入图片描述

3.ArrayList的remove()




其他不常用方法:
1.给定一个容量,如果当前ArrayList实例的容量>给定的容量,那么将容量扩展为给定的容量在这里插入图片描述

2.给定一个集合,删除当前集合中不包含给定集合的元素的所有元素(求差集)。也就是给定集合的元素在当前集合中存在,就不删除,否则就删除。
在这里插入图片描述
3.将集合的容量修改为和当前元素个数相等。内部实现需要进行拷贝操作
在这里插入图片描述

总结:

1.如果不指定初始容量,默认为0,当第一次添加数据时,会变成10,后面添加数据时,判断10如果够用会一直不扩容,当10不够用时,会扩容1.5倍

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值