又是热爱学习的一天… 今天准备学习一下 ArrayList 的源码,研究它都干了什么。
ArrayList 是一种以数组实现的 List,与数组相比,它具有动态扩展的能力,因此也可称之为 动态数组。
ArrayList 实现了 List,提供了基础的添加、删除、遍历等操作。
ArrayList 实现了 RandomAccess,提供了随机访问的能力。
ArrayList 实现了 Cloneable,可以被克隆。
ArrayList 实现了 Serializable,可以被序列化。
先看看 ArrayList 的成员变量,我把它的每一个成员变量都加了注释用以解释这个变量有何作用。
/**
* 默认初始容量,也就是说,使用 new ArrayList() 创建的 ArrayList ,它的初始容量为 DEFAULT_CAPACITY;
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 空数组,使用 new ArrayList(0) 创建 ArrayList 时,使用的数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {
};
/**
* 空数组,使用 new ArrayList() 创建 ArrayList 时,使用的数组。
* 在添加第一个元素的时候,会将这个数组的容量初始化为 DEFAULT_CAPACITY 大小
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
};
/**
* 存放真正的元素数据的数组
* 在添加第一个元素的时候,会使 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 数组容量初始化为 DEFAULT_CAPACITY 大小,这和上面那句话对应起来了。
* 疑问?这里加上 transient 关键字的意思应该是为了不让序列化这个数组里面的内容,也就是我们存进 ArrayList的真实数据,可是经过测验,却可以序列化该数组里面的数据。
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* ArrayList 所包含的实际元素个数,而不是 ArrayList 的长度
* 它和 elementData.length 的区别是,size是实际元素个数,elementData数组里,可能有4个实际元素,6个空元素,所以 elementData.length,代表了 elementData 的长度,它里面包含了空元素。
*/
private int size;
/**
* 传入初始容量,如果容量大于0,就将 elementData 初始化为对应大小。如果等于0,那就使用空数组:EMPTY_ELEMENTDATA。如果小于0,就抛异常了。
* @param initialCapacity 传入指定的容量
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* 如果不传参数,那就使用空数组:DEFAULTCAPACITY_EMPTY_ELEMENTDATA。
* 目前 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的容量为0,要等到添加第一个元素时。它才会初始化为 DEFAULT_CAPACITY 的大小。
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 传入一个 Collection 集合,并调用 toArray() 方法将它里面的内容初始化给 elementData。
* 然后再判断元素个数是否为0,如果为0,就将 elementData 初始化为 EMPTY_ELEMENTDATA 这个空数组
* @param c
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
这里请注意!! 该构造方法中写道:c.toArray might (incorrectly) not return Object[] (see 6260652)
意思是: c.toArray方法返回的可能不是 Object[] 类型,详情见JDK bug编号 6260652
那么这里的 if 判断是说,如果 elementData 不是 Object[] 的类型,就通过 copyOf 这个方法将不是 Object[] 的类型的 elementData 数组(它有可能是String[],int[]…不管它是什么,暂时不管),转换成 Object[] 类型。
这句话有点绕,简单来说就是:如果 elementData 如果不是 Object[] 的类型,那就通过 copyOf 方法把它转换成 Object[] 的类型
为什么这里会有这一步操作呢?为什么 elementData 会不是 Object[] 的类型呢?下面举例子解释:
我们首先来看一下这个构造器里面的内容,我们聚焦在第二层的这个 if 判断上,它判断了 elementData.getClass() 是否等于 Object[].class。那么,elementData 是怎么来的呢?构造器第一句就告诉我们了,他是 Collection 类型的参数 c 调用toArray方法初始化来的。 也就是说,这个 if 判断的是 c.toArray 方法返回的类型是否是 Object[] 类型。
先理解上面这段说明,下面就举一个栗子来分析说明:
// 1、首先使用工具类 Arrays 的 asList 方法,将数组转换成 List。
List<String> myList = Arrays.asList("123456", "ABCDEF");
// 2、模拟进入构造器,构造器需要 Collection 类型的数据,为什么这里传 List 也可以呢?
//因为 ArrayList 实现与 List 接口,List 接口又继承于 Collection 类,所以 ArrayList 也算是 Collection 的子类。所以这里传 List 也 OK。
List<String> arrayList = new ArrayList<>(myList);
// 这里的 myList 就是构造器里面的 c 变量。 myList 为实参, c为形参。
// 根据上面的理解,如果 c.toArray方法返回的类型,也就是这里的 myList.toArray 方法返回的类型不为 Object[] 类型,那就做转换动作,那么 myList 的类型到底是什么呢?
System.out.println(myList.toArray());
打印结果为:
[Ljava.lang.String;@27c170f0
结果说明 c.toArray方法返回的类型 还真有不是 Object[] 类型的,所以构造器里面存在这个if判断操作。
这个时候,可能有同学要问了:
首先看例子中,我们是使用工具类 Arrays 的 asList 方法,将字符串数组转换成 List。那么我们点进 asList 方法看一下源码:
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
return new Arrays.ArrayList<>(a);
}
可以看见它只不过是 new 了一个 Arrays.ArrayList<>(a) 对象而已。
那么这个 Arrays.ArrayList<> 是个什么东西呢?它其实就是 Arrays 类里面的一个内部类,它就在 asList 方法的下面,源码中他们紧挨着。部分代码如下:
Arrays.ArrayList<> 的源码
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
private static final long serialVersionUID = -2764017481108945198L;
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
@Override
public int size() {
return a.length;
}
@Override
public Object[] toArray() {
return a.clone();
}
......
也就是说 Arrays.asList 方法返回的是 Arrays 的一个内部类,它继承于 AbstractList,所以它返回的并不是 java.util.ArrayList。
现在知道 Arrays.asList 方法返回的是什么之后,再来看为什么它调用 toArray() 方法之后,返回的不是 Object[] 类型。
现在将目光聚焦在我贴出来的 Arrays.ArrayList 这个内部类的部分源码,看最后一个方法,这是啥!!!!!其实他重写了父类的toArray()方法,所以当我们在使用 Arrays.asList 方法创建出来的对象的 toArray 方法时,调用的是他自己重写的方法!!
现在再看他是如何重写的,它是 clone 了一份 成员变量 a 的数据,a 是我们创建对象时传进来的,一直往上追,可以发现 a 里面就是我们用例中传的字符串数组,如果我们用例中写的两个int类型的数据,那么此时的 a 就会是这个int数组。所以 toArray 方法其实返回的是一个数组,它并不是一个真正的List。
现在明白为啥例子中打印出来是字符串数组类型的,而不是 Object[] 类型了吧。
满足愿望,那就先使用 myList 增加一个元素试试:
myList.add("HyugaNeji");
结果显示:
java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at AsListTest.main(AsListTest.java:25)
结果是抛异常了,原因是 myList 的类型是 Arrays.ArrayList 类型的,它自己没有实现 add 方法,但是它继承的父类 AbstractList 实现了,所以这里调用 add ,是调用到了它的父类的 add 方法,而它的父类是实现了 add 方法的,其内容如下:
public boolean add(E e) {
add(size(), e);
return true;
}
发现这个方法又调用了 另一个 add 方法,继续往下看,在 147 行的位置发现了这个方法:
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
结果已经很明显了,它的实现方法就是抛异常…
所以,Arrays.asList 创建的所谓的List,是不可以使用 add、set、remove这些方法的,既然这些方法都不能用,我要他有何用,所以才会使用它作参数再创建新的ArrayList。所以才有了构造器里面的那段代码。
至于,Arrays.ArrayList为什么不直接返回一个 java.util.ArrayList 的原因,我现在暂时还没搞清楚,可能是设计模式层面的东西,因为它现在这样实现,不就是适配器模式吗?
先介绍几个保证数组容量安全的核心辅助方法
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
ensureCapacityInternal 方法的意思是 确保 ArrayList 内部容量的意思。如果容量不够装了就进行扩容,确保容量。
它的实现是调用了两个方法,它通过调用 calculateCapacity 方法,拿到返回值,再传给 ensureExplicitCapacity 方法。下面我们先看 calculateCapacity 方法做了什么。
calculateCapacity 方法,看名字就知道它是 计算容量的,它的目的是返回 ArrayList 要存放数据的最小的目标容量。
它通常在 add 数据的时候被使用到,参数 minCapacity 的意思是存放数据需要最小的容量,它是: ArrayList 的实际存放元素个数 + 新增的个数
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
首先判断 elementData 的引用和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的引用是否是相同的,也就是判断是否调用了无参构造器。
因为如果调用了无参构造器,那么 elementData 的容量 == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的容量 == DEFAULT_CAPACITY(原因详见构造器函数)
如果是相同的,那么 elementData 的初始容量就是 DEFAULT_CAPACITY,所以比较 DEFAULT_CAPACITY 和 minCapacity ,谁大返回谁。
换句话说就是:如果调用了无参构造器,那么最小容量最小就是10。这个10容量,可能是几个实际元素 + 几个空元素;
如果不是调用的无参构造器,那么就直接返回 minCapacity。也就是说:我新增元素的个数 + 数组中已有的元素个数 = 最小容量minCapacity,那你最少的给我准备 minCapacity 个数的容量,才够装我的数据。
ensureExplicitCapacity 方法决定了 ArrayList 要不要扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
减8的目的是为了留位置存放数组的长度,因为数组自己不能计算长度,需要留个位置记录一下
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
grow 扩容方法,传入参数(minCapacity) 告诉这个方法,我需要存放的数据的个数,也就是最小的容量,你的容量最小得等于我的个数,不然就不够装啊。
下面看看 巨大容量函数:hugeCapacity(minCapacity) 做了啥。
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
所以 ArrayList 扩容的核心思想是:扩容原来数组长度的1.5倍,然后再将老数组里面的数据拷贝到新数组中
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
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++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
public boolean addAll(Collection<? extends E> c) {
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;
}
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
首先检查索引是否越界,这里只检查是否越上界,如果越上界抛出IndexOutOfBoundsException异常,如果越下界抛出的是 ArrayIndexOutOfBoundsException异常。
然后在返回指定索引位置处的元素;
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;
}
注意:ArrayList删除元素的时候并没有缩小容量。
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) {
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
}
fastRemove(int index)相对于remove(int index)少了检查索引越界的操作,并且不会返回已删除的值。
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
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;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
与retainAll(Collection> c)方法类似,只是这里保留的是不在c中的元素。
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
transient 关键字的意思是不让序列化该关键字修饰的内容
而 ArrayList 它是实现了 java.io.Serializable 接口,这表示它可以被序列化,但是真正存储数据的数组却修饰成了不让序列化。那么这么做有什么意义呢?
原因是:ArrayList 源码里面有 writeObject() 和 readObject() 两个方法,这两个方法声明为private,在只有再 java.io.ObjectStreamClass#getPrivateMethod() 方法中通过反射获取到 writeObject() 这个方法
这样做的目的是:为了自己控制序列化的方式! 因为 elementData 是一个缓存数组,它通常会预留一些容量,等容量不足时再扩充容量, 所以ArrayList的设计者将elementData设计为transient,然后在writeObject方法中手动将其序列化,并且只序列化了实际存储的那些元素,而不是整个数组,这样减少了空间占用。
技 术 无 他, 唯 有 熟 尔。
知 其 然, 也 知 其 所 以 然。
踏 实 一 些, 不 要 着 急, 你 想 要 的 岁 月 都 会 给 你。