目录
ArrayList继承及实现
ArrayList类定义代码如下:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
ArrayList类继承结构图如下:
通过类的定义可以看到ArrayList继承了AbstractList抽象类,实现了List, RandomAccess, Cloneable, Serializable四个接口,其中RandomAccess是支持快速随机访问的标志,ArrayList的克隆依赖于Cloneable,Serializable则用于ArrayList序列化。
这里有个疑问:ArrayList继承了AbstractList抽象类,AbstractList类本身就已经实现了List接口,为什么还要再实现List接口?查阅了很多博客,这里可以参考ArrayList继承了AbstractList,为什么还要实现List接口呢?这篇博客的解答。
ArrayList成员变量
/**
* 序列化版本号
*/
private static final long serialVersionUID = 8683452581122892189L;
/**
* 默认初始化容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 共享空数组实例
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 默认容量共享空数组实例
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* ArrayList中存放元素的数组
*/
transient Object[] elementData;
/**
* ArrayList中当前元素的数量
*/
private int size;
/**
* 数组允许的最大容量
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
集合个别成员变量参考下图理解:
研究完所有的成员变量,难免会产生以下两个疑问:
1、为什么存在两个空数组实例EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA?
通过下面ArrayList构造方法可以看出,EMPTY_ELEMENTDATA用于带初始容量参数构造和带集合参数构造中,当容量为0时,将其赋值给elementData;而DEFAULTCAPACITY_EMPTY_ELEMENTDATA用于无参构造中,当初始化时,直接将其赋值给elementData。
总结起来,EMPTY_ELEMENTDATA是为了优化创建空实例时产生不必要的空数组,使得所有ArrayList空实例都指向同一个空数组(相较于JDK1.7的,每次长度为0都会elementData = new Object[initialCapacity]创建一个空数组,大大浪费了内存空间)。而DEFAULTCAPACITY_EMPTY_ELEMENTDATA是为了确保无参构造创建的实例在添加第一个元素时初始化为默认容量。
2、为什么elementData被transient修饰?
首先被transient关键字修饰的变量将不会被序列化。
注:java序列化是指将对象转化为字节序列的过程,即将java对象转化为二进制字节序列(实际上就是一个byte[]数组);主要用于持久化到磁盘中或者通过网络传输到远程。
这里为什么让elementData不能被序列化?
主要是因为elementData数组可能存在未使用完的情况,这时如果按照数组容量进行序列化的话,会造成空间浪费及性能降低。例如数组capacity为10,size为5,这时便存在5个空的元素,实际上这5个空元素是没有必要进行序列化的,这样便能节省空间及提升性能。
然而elementData真的不能序列化吗?
实际上通过自己写个demo会发现,定义一个ArrayList集合,将其序列化到磁盘后再反序列化回来时,其中的元素并没有丢失,这是怎么回事呢?查看源码发现ArrayList中自定义了writeObject和readObject方法,并且会调用这两个方法来进行序列化和反序列化。另外通过分析源码中的writeObject方法可以看出来,序列化ArrayList使用的是size,即集合实际元素数量,从而证实了上面所说到的问题。
简单demo:
ArrayList<Object> arr = new ArrayList<>();
arr.add(1);
arr.add(2);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data.txt"));
out.writeObject(arr);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.txt"));
System.out.println("反序列化结果:" + in.readObject());
writeObject和readObject方法源码:
/**
* 将ArrayList实例状态保存为流,即序列化
*/
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
/**
* 将流恢复成ArrayList实例,即反序列化
*/
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
ArrayList构造方法
/**
* 带初始容量参数的构造方法
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {// 初始容量大于零,创建initialCapacity容量的数组并将引用赋给elementData
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {// 初始容量大于零,将空数组实例赋给elementData
this.elementData = EMPTY_ELEMENTDATA;
} else {// 初始容量小于零,抛出不合法参数异常(IllegalArgumentException)
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
/**
* 无参构造
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;// 将默认容量空数组实例赋给elementData
}
/**
* 带集合参数的构造方法
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();// 将传入的集合转成数组并赋值给elementData
if ((size = elementData.length) != 0) {// 将elementData数组长度赋值给size,当size不为0时
if (elementData.getClass() != Object[].class)// 如果elementData非Object[]类型
elementData = Arrays.copyOf(elementData, size, Object[].class);// 重新生成一个等长的Object数组赋值给elementData
} else {// 当size等于0时
this.elementData = EMPTY_ELEMENTDATA;// 将空数组实例赋给elementData
}
}
ArrayList核心方法
add及grow方法
/**
* 顺序添加元素
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 判断是否需要扩容并将集合修改次数(modCount)加一
elementData[size++] = e;// size自增并添加元素至elementData数组
return true;
}
/**
* 指定索引位置添加元素,当前位置及其后续位置如果存在元素,则向右移动
*/
public void add(int index, E element) {
rangeCheckForAdd(index);// 检查索引是否越界
ensureCapacityInternal(size + 1); // 判断是否需要扩容并将集合修改次数(modCount)加一
System.arraycopy(elementData, index, elementData, index + 1,
size - index);// 将指定索引位置(index)及其后面的所有元素都向右移一位,空出index出的位置
elementData[index] = element;// 给空出的指定索引位置(index)添加新元素element
size++;// 当前元素数量加一
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {// 当前数组为空
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);// 取默认初始化容量和添加元素后数组实际大小的最大值赋值给minCapacity
}
ensureExplicitCapacity(minCapacity);// 判断是否需要扩容并将集合修改次数(modCount)加一
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;// 集合修改次数加一
// overflow-conscious code
if (minCapacity - elementData.length > 0) // minCapacity超过数组长度
grow(minCapacity);// 进行扩容
}
/**
* 扩容至确保存放至少minCapacity数量个元素
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;// 当前数组的长度赋值给原容量(oldCapacity)
int newCapacity = oldCapacity + (oldCapacity >> 1);// 新容量(newCapacity)=原容量(oldCapacity)的1.5倍(>>带符号右移,oldCapacity >> 1相当于oldCapacity/2)
if (newCapacity - minCapacity < 0) // 扩容后的新容量仍小于minCapacity
newCapacity = minCapacity;// 将minCapacity做为新容量
if (newCapacity - MAX_ARRAY_SIZE > 0) // 扩容后的新容量大于数组允许的最大容量(MAX_ARRAY_SIZE)
newCapacity = hugeCapacity(minCapacity);// 调用hugeCapacity方法选择合适的容量做为新容量
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);// 拷贝当前数组元素生成一个以新容量为长度的新数组并将其引用赋值给elementData(底层采用System.arraycopy方法)
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;// 若minCapacity大于数组允许的最大容量(MAX_ARRAY_SIZE)取Integer.MAX_VALUE,否则取MAX_ARRAY_SIZE
}
/**
* 检查索引是否越界,若越界抛出IndexOutOfBoundsException
*/
private void rangeCheckForAdd(int index) {
if (index > size || index < 0) // 索引值超过当前存储元素个数或小于零
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));// 抛出角标越界异常
}
在ensureExplicitCapacity方法中出现了一个modCount变量,在当前ArrayList中无法搜到,其来自于ArrayList继承的AbstractList抽象类中,主要用于记录ArrayList被修改的次数。
/**
* The number of times this list has been <i>structurally modified</i>.
* Structural modifications are those that change the size of the
* list, or otherwise perturb it in such a fashion that iterations in
* progress may yield incorrect results.
*
* <p>This field is used by the iterator and list iterator implementation
* returned by the {@code iterator} and {@code listIterator} methods.
* If the value of this field changes unexpectedly, the iterator (or list
* iterator) will throw a {@code ConcurrentModificationException} in
* response to the {@code next}, {@code remove}, {@code previous},
* {@code set} or {@code add} operations. This provides
* <i>fail-fast</i> behavior, rather than non-deterministic behavior in
* the face of concurrent modification during iteration.
*
* <p><b>Use of this field by subclasses is optional.</b> If a subclass
* wishes to provide fail-fast iterators (and list iterators), then it
* merely has to increment this field in its {@code add(int, E)} and
* {@code remove(int)} methods (and any other methods that it overrides
* that result in structural modifications to the list). A single call to
* {@code add(int, E)} or {@code remove(int)} must add no more than
* one to this field, or the iterators (and list iterators) will throw
* bogus {@code ConcurrentModificationExceptions}. If an implementation
* does not wish to provide fail-fast iterators, this field may be
* ignored.
*/
protected transient int modCount = 0;
set方法
/**
* 替换指定索引位置(index)处的元素,并返回原值
*/
public E set(int index, E element) {
rangeCheck(index);// 检查索引是否越界
E oldValue = elementData(index);// 取出指定索引位置(index)处的原值
elementData[index] = element;// 给指定索引位置(index)处设置新值
return oldValue;// 返回指定索引位置(index)处的原值
}
通过上面可以发现插入元素有三种方法,分别是add(E e)、add(int index, E element)和set(int index, E element)三个方法,对此三个方法理解可以参考下图:
get方法
/**
* 根据索引返回指定位置的元素
*/
public E get(int index) {
rangeCheck(index);// 检查索引是否越界
return elementData(index);// 返回当前数组指定索引处的元素
}
/**
* 检查索引是否越界,若越界抛出IndexOutOfBoundsException
*/
private void rangeCheck(int index) {
if (index >= size) // 索引值超过当前存储元素个数
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));// 抛出角标越界异常
}
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];// 返回当前数组指定索引处的元素
}
remove方法
/**
* 删除指定索引位置(index)处的元素并返回该元素值,同时将其之后所有元素左移一位
*/
public E remove(int index) {
rangeCheck(index);// 检查索引是否越界
modCount++;// 集合修改次数加一
E oldValue = elementData(index);// 取出指定索引位置(index)处的原值
int numMoved = size - index - 1;// 计算需要移动元素的个数
if (numMoved > 0) // 需要移动元素的个数大于0
System.arraycopy(elementData, index+1, elementData, index,
numMoved);// 将指定索引位置(index)后的所有元素向左移一位(即index+1处索引覆盖了index处)
elementData[--size] = null; // 当前元素数量减一(size-1),并将(size-1)索引位置处的元素置空,等待gc回收
return oldValue;// 返回指定索引位置(index)处的原值
}
/**
* 删除首次出现的指定元素,该元素若存在返回ture,若不存在返回false
*/
public boolean remove(Object o) {
if (o == null) {// 删除元素为空
for (int index = 0; index < size; index++) // 遍历所有元素
if (elementData[index] == null) {// 存在元素为空
fastRemove(index);// 调用fastRmove方法进行删除
return true;// 返回true(删除成功)
}
} else {// 删除元素不为空
for (int index = 0; index < size; index++) // 遍历所有元素
if (o.equals(elementData[index])) {// 存在该元素
fastRemove(index);// 调用fastRmove方法进行删除
return true;// 返回true(删除成功)
}
}
return false;// 返回false(删除失败)
}
/*
* 删除指定索引位置(index)处的元素,同时将其之后所有元素左移一位
* 与remove(int index)区别:不返回删除的元素值,不进行索引越界检查(此处不会越界,前面本就是在合法索引值内进行遍历查找的要删除的元素)
*/
private void fastRemove(int index) {
modCount++;// 集合修改次数加一
int numMoved = size - index - 1;// 计算需要移动元素的个数
if (numMoved > 0) // 需要移动元素的个数大于0
System.arraycopy(elementData, index+1, elementData, index,
numMoved);// 将指定索引位置(index)后的所有元素向左移一位(即index+1处索引覆盖了index处)
elementData[--size] = null; // 当前元素数量减一(size-1),并将(size-1)索引位置处的元素置空,等待gc回收
}
clear方法
/**
* 清空集合所有元素
*/
public void clear() {
modCount++;// 集合修改次数加一
// 遍历集合所有元素,并将所有元素置为空等待gc收集
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;// 当前元素数量置为零
}
ArrayList其它方法
indexOf方法
/**
* 返回指定元素第一次出现的索引值,若不存在,返回-1
*/
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;// 返回对应索引值
}
return -1;
}
/**
* 返回指定元素最后一次出现的索引值,若不存在,返回-1
*/
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;// 返回对应索引值
}
return -1;
}
contains方法
/**
* 判断指定元素在集合中是否存在
*/
public boolean contains(Object o) {
return indexOf(o) >= 0;// 调用indexOf方法进行判断
}
size方法
/**
* 返回集合当前元素数量
*/
public int size() {
return size;// 返回size
}
isEmpty方法
/**
* 判断集合是否为空
*/
public boolean isEmpty() {
return size == 0;// 判断集合当前元素数量是否为零
}
trimToSize方法
/**
* 将ArrayList实例的容量切割为当前实有元素数量(size)
*/
public void trimToSize() {
modCount++;// 集合修改次数加一
if (size < elementData.length) {// 当前元素数量小于elementData数组长度
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);// 当前元素数量为零,将空数组实例赋值给elementData;否则,拷贝当前元素生成一个size大小的新数组并赋值给elementData
}
}
ArrayList迭代器
通常情况下遍历ArrayList有三种方式:
1、普通for循环+索引遍历
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
2、迭代器遍历
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
3、增强for循环遍历(底层也是通过迭代器)
for (String e : list) {
System.out.println(e);
}
在这里,着重介绍下迭代器遍历方式:
/**
* 返回一个迭代器
*/
public Iterator<E> iterator() {
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // 要返回的下一个元素的索引
int lastRet = -1; // 上一个返回元素的索引,如果没有则为-1
int expectedModCount = modCount;// 期望的修改次数,初始值为modCount
public boolean hasNext() {
return cursor != size;// 要返回的下一个元素的索引如果等于集合元素数(size),则返回false,即遍历完了
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();// 校验是否存在并发修改
int i = cursor;// 将要返回的下一个元素的索引赋值给i
if (i >= size) // 如果i大于集合元素数量,抛出NoSuchElementException
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;// 将ArrayList的elementData赋值给内部类中新定义的elementData
if (i >= elementData.length) // 如果i大于等于elementData数组容量,抛出并发修改异常
throw new ConcurrentModificationException();
cursor = i + 1;// 要返回的下一个元素的索引加一(游标向后移动一位)
return (E) elementData[lastRet = i];// 将i赋值给lastRet并返回该索引处的元素
}
public void remove() {
if (lastRet < 0) // 上一个返回元素的索引如果小于零,抛出异常
throw new IllegalStateException();
checkForComodification();// 校验是否存在并发修改
try {
ArrayList.this.remove(lastRet);// 调用ArrayList的remove方法删除lastRet索引处的元素
cursor = lastRet;// 将lastRet赋值给cursor(游标向前移一位)
lastRet = -1;// 将lastRet赋值为-1
expectedModCount = modCount;// 期望修改次数重新赋值modCount
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
...
}
final void checkForComodification() {
if (modCount != expectedModCount) // 期望修改次数与集合修改次数不一致,抛出并发修改异常
throw new ConcurrentModificationException();
}
}
从源码可以看出,ArrayList在其内部定义了一个内部类Itr并实现了Iterator接口。
类中定义了三个成员变量:
cursor:要返回的下一个元素的索引
lastRet:上一个返回元素的索引
expectedModCount:期望的修改次数,初始值为modCount
常用的方法主要为:
hasNext():判断是否存在下一个元素,当游标值(cursor)等于size时,返回false。
next():首先判断是否存在并发修改(expectedModCount和modCount是否相等),然后判断cursor是否超过集合大小和数组长度,接着将cursor自增1,最后将cursor赋值给lastRet返回该下标对应的元素值。起初,cursor = 0,lastRet = -1,后续每调用一次next方法,cursor和lastRet都会自增1。
remove():首先会判断lastRet的值是否小于 0,然后在检查是否存在并发修改(expectedModCount和modCount是否相等),接下来是关键,直接调用ArrayList的remove方法删除下标为lastRet的元素,然后将lastRet赋值给cursor,将lastRet重新赋值为 -1,并将modCount重新赋值给expectedModCount(这里为什么给expectedModCount重新赋值?因为前面调用了ArrayList的remove方法,集合修改次数变化了,这里需要对expectedModCount进行更新)。
总结
1、ArrayList底层实质是一个数组,若调用无参构造生成实例,起初是个空数组,当第一次进行元素添加时,才会将数组扩容为默认长度为10,然后再进行元素添加。在后续使用过程中,若数组容量不够,会继续进行扩容,每次扩容1.5倍,上限为Integer.MAX_VALUE = 0x7fffffff。
2、ArrayList集合之所以可变,其主要依靠的是内部数组复制来实现,究其根源都是通过System.arraycopy(Object src, int srcPos,Object dest, int destPos,int length)方法。
3、ArrayList从抽象类AbstractList中继承了modCount属性,此属性用于记录ArrayList被修改(增、删、改)的次数,其作用主要是用于配合Itr类(迭代器)中成员变量expectedModCount来检验在迭代过程中List对象是否被修改了,如果被修改了则抛出java.util.ConcurrentModificationException异常。(Itr对象在调用next()方法时,首先会调用checkForComodification()方法进行一次检查,checkForComodification()方法主要工作就是比较expectedModCount 和modCount的值是否相等,如果不相等,就认为还有其他对象正在对当前的List进行操作,就会抛出ConcurrentModificationException异常。)
能力有限,如有错误,还望各位大神指正并提出宝贵意见!
参考
jdk源码
Java集合:ArrayList详解
ArrayList继承了AbstractList,为什么还要实现List接口呢?
序列化与ArrayList 的elementData的修饰关键字transient