ArrayList源码分析
一、ArrayList
1、概述
(1)ArrayList是可以动态增长和缩减的索引序列,基于数组实现的List接口
(2)该类封装了一个动态再分配的Object[]数组,每一个类对象都有一个capacity属性,表示封装的Object[]数组的长度,当向ArrayList中添加元素时,该属性值会自动增加
如果向ArrayList中添加大量元素,可使用ensureCapacity()方法一次性增加capacity,可以减少重分配的次数以提高性能
(3)ArrayList的用法和Vector类似,但ArrayList线程不安全,多线程不推荐使用
(4)继承结构
2、ArrayList的数据结构
ArrayList底层的数据结构就是数组,数组的元素类型为Object,即可以存放所有类型数据
二、源码分析
1、继承结构和层次关系
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
(1)继承
ArrayList extends AbstractList
AbstractList extends AbstractCollection
-
ArrayList继承了AbstractList,AbstractList继承了AbstractCollection
-
(类和接口之间的抽象类)为什么ArrayList不直接实现list类,而是AbstractList实现list接口后,再继承?
接口中只能有抽象的方法,而抽象类既可以有抽象方法,也可以有具体的实现方法,List为接口,里面全是抽象方法,AbstractList是一个抽象的类,如此就可以让AbstractList实现部分通用的List方法(即底层类中通用的方法),然后由ArrayList具体的类,来实现自己特有的方法,如此,便可以让代码更简洁,减少代码的复写
(2)接口
-
List
:这其实是一个mistake,因为他写这代码的时候觉得这个会有用处,但是其实并没什么用,但因为没什么影响,就一直留到了现在(https://www.cnblogs.com/zhangyinhua/p/7687377.html) -
RandomAccess:这是一个标记性接口,其作用为用来快速随机存取,有关效率的问题,实现了该接口,使用普通的for循环来遍历,性能更高,这个标记性只是为了让我知道用什么方式去获取数据性能更好
-
Cloneable:可以使用Object.Clone方法,该方法可以克隆
-
java.io.Serializable:实现该序列化接口,表明该类可以被序列化
-
什么是序列化?
能从类变成字节流传输,还能从字节流变为原来的类
-
2、类中的属性
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
// 版本号
private static final long serialVersionUID = 8683452581122892189L;
// 默认初始的容量
private static final int DEFAULT_CAPACITY = 10;
// 空对象数组,如果用户指定容量为0,返回该数组
private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
// 默认空对象数组,如果使用默认构造函数创建,则默认对象内容默认是该数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
// 当前数据对象存放地方,当前对象不参与序列化
transient Object[] elementData;
// 当前数组长度
private int size;
// 数组最大长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}
3、构造方法
(1)无参构造器
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
//private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
}
分析:将默认的空对象数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值给存放数据对象的elementData
(2)参数为int的构造器
public ArrayList(int initialCapacity) {//initialCapacity:初始容量
if (initialCapacity > 0) {//初始容量大于0
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {//初始容量等于0
this.elementData = EMPTY_ELEMENTDATA;//将空对象数组的地址传给elementData存放
//private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
} else {//初始容量小于0,抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
(3)参数为Collection对象的构造器
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();//将数组的地址传给elementData存放
if ((size = elementData.length) != 0) {//数组长度不等于0
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)//每个集合的toarray()的实现方法不一样,所以需要判断一下,如果不是Object[].class类型,那么久需要使用ArrayList中的方法去改造一下
elementData = Arrays.copyOf(elementData, size, Object[].class);//如果size不等于0,执行Arrays.copyOf()方法,将c的内容copy到elementData中
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
分析:每个集合的toarray()的实现方法不一样,所以需要判断一下,如果不是Object[].class类型,那么久需要使用ArrayList中的方法去改造一下
(4)总结
arrayList的构造器做了一件事,初始化存储数据的容器,即elementData,elementData本质上是一个数组
4、方法
4.1 add()方法
(1)add(E): boolean
- 元素方法入口
- 扩容
- 存放数据
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
//新数组创建成功,最低扩容至size++,记录数组长度的size+1
elementData[size++] = e;
return true;
//add成功
}
- ensureCapacityInternal()方法——为扩容确定基础条件,使存放数据的数组为非空
- 判断数组是否为调用无参构造器构造的空数组,则使数组的长度不低于默认的最小长度10
- 若数组不是无参构造器创建的对象则执行ensureExplicitCapacity(minCapacity);为实际长度+1
- 调用ensureExplicitCapacity()方法,判断是否需要扩容
private void ensureCapacityInternal(int minCapacity) {//传入最小容量:size+1,即加入一个元素后的实际长度,用于判断数组长度是否够用
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//判断数组是否为默认空对象数组,这里比较的是地址,判断是否为无参构造器初始化的空数组
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//是默认初始化的,则使最小容量等于默认数组长度10和传入的最小容量minCapacity中的最大值,即使minCapacity大于等于默认初始容量10——确定初始容量10
//private static final int DEFAULT_CAPACITY = 10;
}
ensureExplicitCapacity(minCapacity);//传入最小容量
}
- ensureExplicitCapacity()方法——判断是否需要扩容
- 记录修改次数modCount++
- 数组的实际长度大于用来存放数据的数组的长度,说明数组长度不够用,需要扩容
- 调用grow()扩容
private void ensureExplicitCapacity(int minCapacity) {//传入最小容量
modCount++;//修改次数
//protected transient int modCount = 0;
// overflow-conscious code
if (minCapacity - elementData.length > 0)//数组的实际长度大于用来存放数据的数组的长度,说明数组长度不够用,需要扩容
grow(minCapacity);//传入数据实际的最小容量
//容量增长成功,扩容的新数组创建完成
}
- grow()方法——扩容
- 默认是新数组长度=1.5*老数组长度
- 若还是放不下,则使新数组长度等于数组的实际长度
- 若数组长度超出了最大容量限制
- 则给新数组长度能给的最大值
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//用来存放数据的数组的长度
int newCapacity = oldCapacity + (oldCapacity >> 1);//算数右移1,则新数组的长度等于1.5被的老数组长度
if (newCapacity - minCapacity < 0)//如果放不下
newCapacity = minCapacity;//令新数组的长度直接等于数据的实际长度
if (newCapacity - MAX_ARRAY_SIZE > 0)//如果新数组长度高出了容量限制
// private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
newCapacity = hugeCapacity(minCapacity);//将能给的最大值给新数组长度
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//使elementData指向新的从老elementData数组copy来的新长度的数组
//返回,新数组创建完成
}
- hugeCapacity()——最大容量限制
private static int hugeCapacity(int minCapacity) {//最大容量限制
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
(2) add(int, E):void
在下标为int的地方插入元素
public void add(int index, E element) {
rangeCheckForAdd(index);//判断下标合理性
ensureCapacityInternal(size + 1); // Increments modCount!!扩容
//System.arraycopy()方法用来在插入元素之后,要将index之后的元素都往后移一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//在index下标放入元素
elementData[index] = element;
size++;//当前数组长度增加1
}
private void rangeCheckForAdd(int index) {//传入下标
if (index > size || index < 0)//判断下标合理性,不合理抛出异常
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
(3)总结
- 若调用无参构造器,第一次add后容器能放置的对象长度为10
- 若调用有参构造器,则会扩容1.5倍
- 扩容有最大上限
4.2 get(int):E
返回指定位置上的元素
public E get(int index) {
rangeCheck(index);//检查下标的合理性
return elementData(index);//返回数组下标相应的元素
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
4.3 set(int, E):E
设置新元素,返回旧元素
public E set(int index, E element) {
rangeCheck(index);//检查下标合理性
E oldValue = elementData(index);//将旧元素存放在oldValue中
elementData[index] = element;//放入新元素
return oldValue;//返回旧元素
}
4.4 contains(Object):boolean
public boolean contains(Object o) {
return indexOf(o) >= 0;//返回是否存在,若存在下标大于等于0,不存在返回-1
}
index(Object):int:遍历查找指定元素
public int indexOf(Object o) {//遍历查询
if (o == null) {//查询null元素
for (int i = 0; i < size; i++)
if (elementData[i]==null)//比较地址是否为null
return i;//找到返回下标
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))//比较元素内容是否相等
return i;
}
return -1;//没找到返回-1
}
4.5 remove()删除方法
(1)remove(int):E
public E remove(int index) {
rangeCheck(index);//检查下标合理性
modCount++;//修改次数++
E oldValue = elementData(index);//记录旧元素
int numMoved = size - index - 1;//被删元素后面元素的新下标
if (numMoved > 0)//合理性判断
System.arraycopy(elementData, index+1, elementData, index,
numMoved);//移动元素
elementData[--size] = null; // clear to let GC do its work,末尾置空
return oldValue;
}
(2)remove(Object):boolean
public boolean remove(Object o) {
if (o == null) {//查找元素为空
for (int index = 0; index < size; index++)
if (elementData[index] == null) {//找到索引
fastRemove(index);//删除元素
return true;//返回
}
} else {//非空
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {//用来删除指定元素,和remove一样
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
(3)clear():void
将容器elementData中每个元素置空null等待垃圾回收将其回收
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)//遍历置空
elementData[i] = null;
size = 0;
}
(4)removeAll(Collection):boolean
批量删除c
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);//c为null报错
return batchRemove(c, false);
}
- batchRemove:检测两个集合是否有交集
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;//向上转换
int r = 0, w = 0;//r控制循环,w记录交集个数
boolean modified = false;
try {
for (; r < size; r++)//循环size次,即遍历elementData是否有c
if (c.contains(elementData[r]) == complement)//如果没找到
elementData[w++] = elementData[r];//将没找到的元素放入新数组
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {//异常,将剩下的元素放入新数组
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {//数组长度减少
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;//将w下标及之后之后全部置空,因为w在前面是后++所以从w开始
modCount += size - w;//操作次数更新
size = w;//数组大小更新
modified = true;//删除成功
}
}
return modified;
}
public boolean contains(Object o) {//有,返回true,无返回false
return indexOf(o) >= 0;//indexOf遍历,若有返回下标,无返回-1
}
三、总结
1、arrayList可以存放null
2、arrayList本质上是elementData数组
3、arrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是gorw()方法
4、arrayList中removeAll(collection c)和clear()的区别就是removeAll可以删除批量指定的元素,而clear是全是删除集合中的元素
5、arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,有移动很多数据才能达到应有的效果
6、arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环