ArrayList是使用比较多的一个List,它的底层实现是使用的是一个数组,从继承性来讲,它继承了AbstractList的接口,并实现了List、RandomAccess、Cloneable和Serializable序列化接口。
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
}
首先来看一下ArrayList中的一些变量:
// 序列化ID
private static final long serialVersionUID = 8683452581122892189L;
// ArryList数组的默认的容量
private static final int DEFAULT_CAPACITY = 10;
// 一个空的数组,当用户指定容量为0时,返回该数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 一个空数组的实例
// - 当用户没有指定ArrayList的容量时,也就是调用无参构造的时候,返回的是该数组 刚创建的ArrayList,其中的数据量为0
// - 当用户第一次添加元素时,该数组将会扩容,变为默认的10(DEFAULT_CAPACITY) 通过ensureCapacityInternal()
// 它于EMPTY_ELEMENTDATA的区别是,该数组是默认返回的,EMPTY_ELEMENTDATA是用户指定容量为0时返回的
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// ArrayList基于数组实现,该数组保存数据,ArrayList的容量就是该数组的长度
// - 该值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
// - 当用户第一次添加元素进入ArrayList中时,该数组将扩容为DEFAULT_CAPACITY
// - transient 修饰element表示不序列化这个变量
transient Object[] elementData;
// ArrayList中元素的个数
private int size;
/**
* 数组缓冲区最大容量
* The maximum size of array to allocate.
* - 一些VM会在一个数组中存储某些介质 为什么减8的原因
* - 尝试分配这个最大存储容量,可能会导致OutOfMemoryError(当该值 > VM 限制时)
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
还有一个很重要的变量,它不在ArrayList中,它在AbstractList中,它就是modCount
。
protected transient int modCount = 0;
这个变量同样不参与序列化,这个变量的作用是记录ArrayList的修改次数,记录它的修改次数有什么用呢?这个会在后面说明。
上面也说道的一些变量它们与ArrayList的构造器相关,看了这块就会明白上面的话了。
ArrayList有三个构造器,分别是指定容量大小的,无参的和一个参数为Collection的构造器。
指定大小的构造器:
/**
* 创建一个初始容量的,空的ArrayList
* @param initialCapacity 初始容量
* @throws IllegalArgumentException 当初始容量为小于0时抛出
* is negative
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 这里当指定的初始容量为0时返回
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: " +
initialCapacity);
}
}
无参的构造器:
/**
* 无参构造,返回DEFAULTCAPACITY_EMPTY_ELEMENTDATA
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
参数为Collection的构造器:
/**
* 创建一个包含collection的ArrayList
*
* @param c 要放入ArrayList中的集合,其内元素将全部会添加到新建的ArrayList中
* @throws NullPointerException 当c为空时
*/
public ArrayList(Collection extends E> c) {
// 将集合转化成数组
elementData = c.toArray();
if ((size = elementData.length) != 0) { // 把数组的长度赋给ArrayList的size,并判断是否为空,当不等于空
// c.toArray might (incorrectly) not return Object[] (see 6260652)
// c.toArray 可能不会返回Object[] ,这是一个bug
if (elementData.getClass() != Object[].class)
// 若c.toArray()返回的数组不是Object[],则利用Arrays.copyOf();来构造一个大小为size的Object[]
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 替换空的数组
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
/**
* 将数组的缓冲区调整到实际ArrayList存储元素的大小,即elementData = Arrays.copOf(elementData, size)
* 该方法有用户手动调用目的是为了减少空间的浪费
*/
public void trimToSize() {
modCount++; // 这个是记录ArrayList修改的次数
// 当实际的大小 < 缓冲区大小时
// 如调用默认的构造函数后,刚添加第一个元素,此时它的缓冲区的大小为10, 可以调用该函数调整
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
// 复制指定的数组,截取或用 null 填充(如有必要),以使副本具有指定的长度。
// 对于在原数组和副本中都有效的所有索引,这两个数组将包含相同的值。
// 对于在副本中有效而在原数组无效的所有索引,副本将包含 null。当且仅当指定长度大于原数组的长度时,
// 这些索引存在。所得数组和原数组属于完全相同的类。
@SuppressWarnings("unchecked")
public static T[] copyOf(T[] original, int newLength) {
// 这个方法调用了下面的这个方法
return (T[]) copyOf(original, newLength, original.getClass());
}
// 这个方法和上面的方法不同的地方就是它有一个名为newType的参数,所以它最终返回的数组类型为newType。
public static T[] copyOf(U[] original, int newLength, Class extends T[]> newType) {
@SuppressWarnings("unchecked")
// 判断newType是不是Object[]类型的
T[] copy = ((Object)newType == (Object)Object[].class)
// 如果是直接new
? (T[]) new Object[newLength]
// 如果不是,通过调用这个方法获取,newType.getComponentType()获取数组组件类型的Class
// 这个在往下就到native层了
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
// 下面的这个方法是一个native层的一个方法,在后面也用的比较多,这个是用来实现数组复制的
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
System.arraycopy()
的原型如下:
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
它的四个参数的作用如下:
参数 | 作用 |
---|---|
src | 原数组,也就是需要复制的数组 |
srcPos | 原数组的复制的起始位置 |
dest | 目标数组 |
destPos | 目标数组中的起始位置 |
length | 复制的长度 |
下面的是Array.copyOf()和System.arraycopy()方法的简单使用:
import java.util.Arrays;
public class ArrayTest {
public static void main(String[] args) {
// Array.copyOf()的用法
System.out.println("-------------Arrays.copyOf()的用法示例-------------");
Object[] array = new Object[1];
array[0] = "Hello";
array = Arrays.copyOf(array, 3);
for(Object o : array) {
System.out.print((String)o);
}
System.out.println();
array[1] = " ";
array[2] = "World!";
for(Object o : array) {
System.out.print((String)o);
}
System.out.println("-------------System.arraycopy()的用法示例---------------");
int[] arraySrc = new int[9];
for (int i = 0; i < 9; i++) {
arraySrc[i] = i;
}
int[] arrayDest = new int[10];
System.arraycopy(arraySrc, 0, arrayDest, 0, 9);
for (int i = 0; i < 10; i++) {
System.out.println(arrayDest[i]);
}
}
}
/**
* Appends the specified element to the end of this list.
* 将指定的元素添加到ArrayList最后的位置
*
* @param e element to be appended to this list
* @return true (as specified by {@link Collection#add})
*/
public boolean add(E e) {
// 确保ArrayList的容量
// 保证要存多少个元素,就分配多少的空间
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/**
* 私有的方法:明确ArrayList的容量
* 用于内部优化,保证空间资源不浪费:尤其在add()方法中
*
* @param minCapacity 指定最小容量
*/
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
这个方法调用了calculateCapacity()方法,这个方法时一个私有的静态方法。
/**
* 私有的静态方法:明确ArrayList的容量
*
* @param elementData
* @param minCapacity
* @return 返回的是经过计算后的ArrayList的容量
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果elementData为空的话,返回默认用量和minCapacity中的较大者
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 比较指定容量和默认容量那个大,返回较大的
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 如果不为空,返回minCapacity
return minCapacity;
}
/**
* 私有的方法:明确ArrayList的容量
* 用户内部优化,保证空间资源不浪费:尤其在add()方法中
* 防止溢出
* @param minCapacity
*/
private void ensureExplicitCapacity(int minCapacity) {
// 将修改的统计次数加1
modCount++;
// overflow-conscious code
// 防止溢出,确保最小容量 > 数组缓冲区的容量
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
这个是ArrayList中一个比较重要的内容:数组扩容。
/**
* 私有方法:扩容,以确保能存储minCapacity个元素
* - 扩容计算:newCapacity = oldCapacity + (oldCapacity >> 1); 扩容是当前容量的1.5倍
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity 指定最小容量
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 扩容的策略是newCapacity = oldCapacity oldCapacity / 2
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果使用这种策咯扩容之后比最小容量小,那么让newCapacity = minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 也就是newCapacity > MAX_ARRAY_SIZE 也就是新的容量比最大的容量还要大
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity); // 其实这个方法当minCapacity >MAX_ARRAY_SIZE 时,返回Integer.MAX_VALUE
// minCapacity is usually close to size, so this is a win: 这里使用了Arrays.copyOf()这个方法进行扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
下面的就是hugeCapacity(int minCapacity)的源码。
/**
* 私有方法:最大容量分配,最大分配Integer.MAX_VALUE
*
* @param minCapacity
* @return
*/
private static int hugeCapacity(int minCapacity) {
// 小于0,抛出异常 OutOfMemoryError
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 指定最小的容量大于最大能分配的容量,返回Integer.MAX_VALUE,否则返回能分配的最大值
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
/**
* 将该数组转化成Object数组
* - 包含ArrayList的所有元素
* - 对返回的数组操作,不会影响ArrayList
* - 元素的存储与ArrayList一致
* @return an array containing all of the elements in this list in 返回一个包含ArrayList元素的数组
* proper sequence
*/
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
这里可以看到这个方法就是用了Arrays.copy()方法。
/**
* 返回ArrayList元素组成的数组
*/
@SuppressWarnings("unchecked")
public T[] toArray(T[] a) {
// 若a.length < size时新建一个T[]数组
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
// 若数组a的大小 >= ArrayList中元素的个数,则将ArrayList中的元素全部拷贝到a数组中
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
这个方法可能看一些JDK的解释也看的稀里糊涂的(我就是这样)。
通过看它的源码就会发现,它有两种情况:
1. 数组的长度小于ArrayList的size时(a.length < size
),调用之前介绍的Arrays.copyOf()
方法复制elementData中的元素(也就是复制ArrayList中的元素)。
2. 数组的长度大于等于ArrayList的size,将elementData中的元素从0开始赋值给a。然后将a[size]赋值为null。
下面的是它的一个测试示例:
import java.util.*;
public class ArrayListToArray {
public static void main(String[] args) {
System.out.println("---------第一种情况-----------");
String[] array1 = {"Hello", " ", "World!"};
ArrayList list1 = new ArrayList();
list1.add("my");
list1.add("name");
list1.add("is");
list1.add("emmmmm");
String[] arrayReturn1 = list1.toArray(array1);
System.out.println("array数组:");
for (String str : array1) {
System.out.println(str);
}
System.out.println("list数组:");
for (String str : list1) {
System.out.println(str);
}
System.out.println("arrayReturn数组:");
for (String str : arrayReturn1) {
System.out.println(str);
}
System.out.println("---------第二种情况-----------");
String[] array2 = {"Hello", " ", "World!", "Hello", " ", "ArrayList", "!"};
ArrayList list2 = new ArrayList();
list2.add("my");
list2.add("name");
list2.add("is");
list2.add("emmmmm");
String[] arrayReturn2 = list2.toArray(array2);
System.out.println("array数组:");
for (String str : array2) {
System.out.println(str);
}
System.out.println("list数组:");
for (String str : list2) {
System.out.println(str);
}
System.out.println("arrayReturn数组:");
for (String str : arrayReturn2) {
System.out.println(str);
}
}
}
/**
* 在这个ArrayList中指定位置插入指定的元素
* - 在指定位置之前插入新的元素,原先的index位置往后移动一位
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
rangeCheckForAdd(index); // 检查是否越界
ensureCapacityInternal(size + 1); // Increments modCount!!这个方法和上面的那个方法的作用相同
// 第一个是要复制的数组,第二个是开始位置,第二个复制到哪,第四个是复制的长度
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
这里有一个越界检查的方法,这个方法很简单,是一个为add提供的范围检查的方法,若果越界则抛出IndexOutOfBoundsException
/**
* 范围检查用于add和addAll
* A version of rangeCheck used by add and addAll.
*/
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
然后这个方法还调用一个构建越界信息的方法,在这里不得不说:真的是把复用做到了极致。
private String outOfBoundsMsg(int index) {
return "Index: " + index + ", Size: " + size;
}
/**
* 删除指定位置的元素
* index后的索引元素一次左移一位
* @param index the index of the element to be removed index指定位置
* @return the element that was removed from the list 被移出的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
// 检查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;
这个方法的源码还是很好懂的,ArrayList中有关数组复制的几乎都是用的System.arraycopy()这个方法。
/**
* 移出list中指定的第一个元素(符合条件索引的最低值)
* 如果ArrayList中不包含这个元素,那么ArrayList将不会被改变
* @param o element to be removed from this list, if present
* @return true if this list contained the specified element
*/
public boolean remove(Object o) {
// 如过o = null
if (o == null) { // 遍历找到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;
}
因为ArrayList集合中的元素可以是null,所以如果传入的是null,那么首先它会去寻找第一个为null的元素。
如果不是的话,那么他会去使用元素的equals方法去判断是否相等。这里也就是我们有时候要重写类的equals方法的原因了。
上面的方法中调用了一个fastRemove(index),这个方法是ArrayList的一个私有的方法。
它的源码如下:
/*
* 快速删除第index个元素
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
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 将最后一个元素清除
}
这个方法的作用是将ArrayList的元素全部清空。
/**
* 删除该list中的所有元素
* - 他会将数组缓冲区所有元素置为null
* - 清除之后,打印list会出现[], 而不是null, 这是因为toSting()和迭代器处理了
* Removes all of the elements from this list. The list will
* be empty after this call returns.
*/
public void clear() {
modCount++;
// clear to let GC do its work 这里把他们去不置为空,让GC工作
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
addAll(Collection
/**
* 将一个集合中的所有元素追加到list的末尾
* - ArrayList是线程不安全的,当一个线程正在将c中的元素添加到list中,
* 但同时另一个线程在更改c中的元素,可能会产生问题
* @param c collection containing elements to be added to this list 要追加的集合
* @return true if this list changed as a result of the call
* @throws NullPointerException if the specified collection is null
*/
public boolean addAll(Collection extends E> c) {
// 将c转化为一个object数组
Object[] a = c.toArray();
// 要追加的元素的个数
int numNew = a.length;
// 扩容
ensureCapacityInternal(size + numNew); // Increments modCount
// 复制
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
上面提到了elementData和size不参与序列化,但是平常使用的时候ArrayList是可以正常的序列化的,因为它有自己的序列化和反序列化的方法。
序列化方法writeObject()
/**
* 私有方法
* 将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();
}
}
这里就体现出modCount的作用了,如果正在序列化的时候ArrayList被修改了,那么modCount的值肯定会变大,如果序列化结束后modCount的大小和之前记录的大小不一样大,那么肯定ArrayList在序列化的过程中被修改了,那么就会抛出ConcurrentModificationException异常。
readObject()
/**
* 私有的方法
* 以反序列化中重构ArrayList实例
* Reconstitute the ArrayList instance from a stream (that is,
* deserialize it).
*/
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
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, 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();
}
}
}
readObject()方法将那么没有参与序列化的属性读取出来。
/**
* 实现Cloneable接口,深度复制
* Returns a shallow copy of this ArrayList instance. (Th
* elements themselves are not copied.)
*
* @return a clone of this ArrayList instance
*/
public Object clone() {
try {
// Object的克隆会复制本对象的其内的基本类型和String类型,但不会复制对象
ArrayList> v = (ArrayList>) super.clone();
// 将elemetData进行复制
v.elementData = Arrays.copyOf(elementData, size);
// 因为复制出来的是新的,没有被修改过,所以modCount初始化为0
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
剩余的方法还是比较多的但是都比较好理解,与数组中元素移动相关的都使用到了System.arraycopy()这个方法。所以在这里就不多讲了。
下面将一些比较重要的方法