Java集合源码分析之ArrayList
ArrayList简介
从上图可以看到,ArrayList是集合框架中List接口的一个实现类,它继承了AbstractList类,实现了List, RandomAccess, Cloneable, Serializable。
- 实现List接口,对数组的基本增删改查操作。
- 实现RandomAccess接口,快速随机访问功能。
- 实现Cloneable接口,可以被复制,clone()方法。
- 实现Serializable接口,支持序列化,可以序列化传输。保证类的一致。
在使用基本数组保存数据的时候,数组长度固定对于增删数据来说非常不方便,ArrayList可以动态的增删数组中的元素,和Vector但是ArrayList是不同步的,线程不安全的。
ArrayList成员变量
类的成员变量介绍:
//可序列化版本号
private static final long serialVersionUID = 8683452581122892189L;
//初始化默认数组长度为10
private static final int DEFAULT_CAPACITY = 10;
//空数组,用户指定数组长度为0时返回此数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//空数组,当用户没有指定数组长度时返回此数组,
//用户第一次添加元素时,数组扩容成默认容量DEFAULT_CAPACITY
//此数组时默认返回的,而EMPTY_ELEMENTDATA数组返回是在用户指定数组长度为0时返回的。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//用来缓存数组存储的元素,ArrayList的容量和他的长度相等,
//第一次添加元素时会被扩充至DEFAULT_CAPACITY
transient Object[] elementData;
//ArrayList的长度,包含元素个数
private int size;
构造方法
1. 参数为指定容量:
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//创建传入大小的elementData数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//传入长度为0时空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
//传入负数报错
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
2. 空参构造
public ArrayList() {
//创建默认空数组,初始化长度为10
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
3. 参数为集合元素
public ArrayList(Collection<? extends E> c) {
//传入集合转换为数组赋给elementData
elementData = c.toArray();
if ((size = elementData.length) != 0) {
//如果不是Object类型的数组,转化为Object类型数组
//调用了Arrays的copyof方法,在Arrays中用System.arraycopy拷贝数组实现
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//长度为0,空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
ArrayList关于容量的方法
1. trimToSize改变数组长度为实际长度
集合中实际的元素个数size和数组长度elementData.length可能并不相等,在不需要长度大于实际元素个数时,可以使用此方法,减少内存浪费。
public void trimToSize() {
//每次对数组进行修改操作都会modCount自加,理解为数组版本号
modCount++;
if (size < elementData.length) {
//实际元素个数小于数组长度并且实际元素个数为0时返回空数组
//否则复制数组并且长度为以及元素实际个数的数组
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
2. ensureCapacity确认数组长度如果需要就扩容
public void ensureCapacity(int minCapacity) {
//传入的最小容量值大于目前数组长度,并且当前数组不是默认空数组,
//并且传入的容量值大于默认容量值(10),对数组增加为传入的容量值
if (minCapacity > elementData.length
&& !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
&& minCapacity <= DEFAULT_CAPACITY)) {
//对数组操作,数组版本自加
modCount++;
//增加数组长度
grow(minCapacity);
}
}
3. grow扩容
private Object[] grow(int minCapacity) {
//返回数组扩容后的数组,具体会不会扩充到传入的容量newCapacity方法再做判断
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
private Object[] grow() {
//返回一个增加一个容量的数组
return grow(size + 1);
}
4. newCapacity容量判断
传入一个数组的容量,这个方法会判断这个容量,
private int newCapacity(int minCapacity) {
int oldCapacity = elementData.length;
//新的容量为原容量1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果这个1.5倍的容量小于传入的容量
if (newCapacity - minCapacity <= 0) {
//如果数组是空数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
//返回默认容量10和传入的容量中的大值
return Math.max(DEFAULT_CAPACITY, minCapacity);
//如果传入的容量为负数报错
if (minCapacity < 0)
throw new OutOfMemoryError();
//返回传入的容量(1.5倍的容量小于传入的容量并且数组不是空数组并且传入的容量大于0)
return minCapacity;
}
//传入的容量是不是小于最大容量返回传入的容量,否则返回最大容量判断
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
5. hugeCapacity最大容量判断
判断传入的容量值是不是大于最大容量值
private static int hugeCapacity(int minCapacity) {
//如果传入的容量小于0报错
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//如果传入的容量值大于最大容量值返回最大的Integer值2^31次方,2147483648
return (minCapacity > MAX_ARRAY_SIZE)
? Integer.MAX_VALUE
: MAX_ARRAY_SIZE;
}
最大容量值定义:
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
最大的Integer数-8,为什么要减去8?
- 为了存储数组的头信息
- 为了防止内存溢出
增删改查及其他方法
1. size返回集合长度
public int size() {
return size;
}
2. isEmpty是否为空
public boolean isEmpty() {
return size == 0;
}
3. contains是否包含某个元素
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
4. indexOf查找元素索引
public int indexOf(Object o) {
//如果传入的元素是空的判断
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
//遍历数组查找值相同的元素返回其索引
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
//没有这个元素返回-1
return -1;
}
5. lastIndexOf元素最后出现的索引
public int lastIndexOf(Object o) {
//传入元素是空的判断
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
//从后向前遍历数组如果值相等返回索引
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
//没有找到返回-1
return -1;
}
6. clone克隆ArrayuList
public Object clone() {
try {
//声明一个新的ArrayList v
ArrayList<?> v = (ArrayList<?>) super.clone();
//调用Arrays类的copyOf方法复制数组传给v的elementData
v.elementData = Arrays.copyOf(elementData, size);
//将v的modCount初始化为0
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
7. toArray转换为数组
public Object[] toArray() {
//集合的elementData复制一份返回
//为了安全没有直接返回这个集合的elementData数组而是新复制一份
return Arrays.copyOf(elementData, size);
}
8. toArray(T[] a)转换为数组
public <T> T[] toArray(T[] a) {
if (a.length < size)
//传入的数组长度小于集合长度,创建一个新的数组复制元素返回
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
//复制集合元素给a数组
System.arraycopy(elementData, 0, a, 0, size);
//如果a的长度大于集合的长度 将最后数组一位置null
if (a.length > size)
a[size] = null;
return a;
}
9. get获取索引处元素
public E get(int index) {
//判断传入索引是否合法再0到size之间
Objects.checkIndex(index, size);
//返回对应索引处的元素
return elementData(index);
}
10.set替换指定索引处的元素
public E set(int index, E element) {
//判断传入索引是否合法
Objects.checkIndex(index, size);
//先取到索引处原来的元素
E oldValue = elementData(index);
//给索引处赋新元素
elementData[index] = element;
//返回索引处原来的元素
return oldValue;
}
11.add添加一个元素
private void add(E e, Object[] elementData, int s) {
//数组长度是否等于集合size
if (s == elementData.length)
//要添加元素数组长度不够所以扩容
elementData = grow();
//给size处赋传进来的元素e
elementData[s] = e;
size = s + 1;
}
直接向集合尾部添加元素方法:
public boolean add(E e) {
//对数组做修改操作modCount先自加
modCount++;
add(e, elementData, size);
return true;
}
向指定的索引位置添加一个元素:
public void add(int index, E element) {
//传入索引检查是否合法
rangeCheckForAdd(index);
//对元素修改操作modCount先自加
modCount++;
final int s;
Object[] elementData;
//判断数组长度与集合size是否相等
if ((s = size) == (elementData = this.elementData).length)
//相等的情况数组需要增容
elementData = grow();
//拷贝数组,将原数组索引处(包括索引)后的元素拷贝到新数组中,从index+1位置开始
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
//向索引处添加元素
elementData[index] = element;
//集合元素个数加一
size = s + 1;
}
12.remove删除元素
删除指定索引处的元素:
public E remove(int index) {
//判断传入索引是否合法
Objects.checkIndex(index, size);
//对元素修改操作modCount先自加
modCount++;
//取到索引处原来的元素
E oldValue = elementData(index);
//索引处元素删除后,需要向前移动的元素个数
int numMoved = size - index - 1;
if (numMoved > 0)
//将索引+1到数组结尾的元素复制到索引处到数组结尾
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将size-1处的索引置为null垃圾回收
elementData[--size] = null;
//返回删除的元素
return oldValue;
}
删除指定元素:
public boolean remove(Object o) {
//需要删除的元素为null时
if (o == null) {
//遍历数组找到null元素的索引
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
//将索引位置删除
fastRemove(index);
return true;
}
} else {
//遍历数组找到o元素的索引
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
//将索引位置删除
fastRemove(index);
return true;
}
}
return false;
}
快速删除:
private void fastRemove(int index) {
//对元素修改操作modCount先自加
modCount++;
//索引处元素删除后,需要向前移动的元素个数
int numMoved = size - index - 1;
if (numMoved > 0)
//将索引+1到数组结尾的元素复制到索引处到数组结尾
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将size-1处的索引置为null垃圾回收
elementData[--size] = null;
}
13.删除所有元素
public void clear() {
//对元素修改操作modCount先自加
modCount++;
final Object[] es = elementData;
for (int to = size, i = size = 0; i < to; i++)
es[i] = null;
}
14.addAll添加集合到集合
传入参数是要添加的集合:
public boolean addAll(Collection<? extends E> c) {
//将传入的集合转换为数组
Object[] a = c.toArray();
//对元素修改操作modCount先自加
modCount++;
int numNew = a.length;
//传入集合为空返回false
if (numNew == 0)
return false;
Object[] elementData;
final int s;
//当前数组长度不够保存新的数组元素时数组扩容
if (numNew > (elementData = this.elementData).length - (s = size))
elementData = grow(s + numNew);
//复制数组到扩容后的数组
System.arraycopy(a, 0, elementData, s, numNew);
//集合个数增加
size = s + numNew;
return true;
}
向指定索引处复制一个集合
public boolean addAll(int index, Collection<? extends E> c) {
//索引检查是否合法
rangeCheckForAdd(index);
//将传入的集合转换为数组
Object[] a = c.toArray();
//对元素修改操作modCount先自加
modCount++;
int numNew = a.length;
//传入集合为空返回false
if (numNew == 0)
return false;
Object[] elementData;
final int s;
//当前数组长度不够保存新的数组元素时数组扩容
if (numNew > (elementData = this.elementData).length - (s = size))
elementData = grow(s + numNew);
//将要复制到的索引位置到数组结尾的元素复制到当前位置+要复制的数组长度
int numMoved = s - index;
if (numMoved > 0)
System.arraycopy(elementData, index,
elementData, index + numNew,
numMoved);
//将要复制的数组复制到指定的索引处
System.arraycopy(a, 0, elementData, index, numNew);
//集合个数增加
size = s + numNew;
return true;
}
15.removeRange删除指定范围的元素
protected void removeRange(int fromIndex, int toIndex) {
//起始位置索引大于结束位置索引报错
if (fromIndex > toIndex) {
throw new IndexOutOfBoundsException(
outOfBoundsMsg(fromIndex, toIndex));
}
//对元素修改操作modCount先自加
modCount++;
shiftTailOverGap(elementData, fromIndex, toIndex);
}
private void shiftTailOverGap(Object[] es, int lo, int hi) {
//复制指定一段位置的元素向前滑动
System.arraycopy(es, hi, es, lo, size - hi);
//将复制过的元素置null
for (int to = size, i = (size -= hi - lo); i < to; i++)
es[i] = null;
}
ArrayList的迭代器
ArrayList的迭代器部分的源码分析见这里。
总结
-
什么是ArrayList?
是集合框架中List的一个实现类,底层是用数组实现的,可以动态的对数组进行增删改查,当容量不足时自动扩容,默认容量为10,自动扩充容量为当前容量的1.5倍,是用System.arraycopy()复制为新的数组实现的。查找快,但是增删性能较查,因为每次增删数据都会需要复制数组,线程不安全。
-
ArrayList如何实现自动扩容?
在grow()方法中实现扩容,具体的是在newCapacity()方法中来确定扩容的大小,自动扩容1.5倍,实现自动扩容是通过Arrays.copyof()复制原数组到新的容量的数组中去。
-
ArrayList扩容最大容量?
ArrayList不能无线扩容,最大容量为
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
但是如果需要的容量大于上面的值,ArrayList会扩容到Integer.MAX_VALUE = 2 ^ 31,关于MAX_ARRAY_SIZE = Integer.MAX_VALUE – 8为什么需要减8,首先数组需要保存头信息,其次是为了放置溢出。