在解析源码前,我们先要认识两个重要的数组函数。它们分别是 ①、public static native void System.arraycopy(Object srcArray,int srcPos,Object destArray,int destPos,int length);
②、public staticT[] copyOf(U[] original, int newLength, Class Extends T[]> newType); 1、首先说一下第一个函数 它的参数列表如下所示:
参数 | srcArray | srcPos | destArray | desPos | length |
含义 | 原数组 | 原数组开始位置 | 目标数组 | 目标数组开始位置 | 拷贝长度 |
一句话来说,就是将srcArray从srcPos(包括srcPos)开始的length个元素拷贝到destArray以desPos开始的length个元素上。 2、接下来是第二个函数:
参数 | original | newLength | newType |
含义 | 源数组 | 新数组长度 | 目标类型 |
它的方法体是这样的:
{
T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[])Array.newInstance(newType.getComponentType(),newLength);
System.arraycopy(original, 0 , copy, 0 ,Math.min(original.length, newLength));
Return copy;
}
第一行代码 T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[])Array.newInstance(newType.getComponentType(), newLength);
先是判断目标类型是否是Object[],如果是就直接实例化一个newLength的Object数组,如果这个数组已经有确切类型指定比如String,那么就用newType.getComponentType()这个方法得出数组中的元素类型是java.lang.String,然后再用Array.newInstance(newType.getComponentType, newLength)实例化出一个元素类型为String的数组。当然,源码中用的都是泛型指代,这里为了便于理解直接用String讲解
第二行代码 System.arraycopy(original, 0 , copy, 0 ,Math.min(original.length, newLength));
则是赋予copy数组具体内容。
他将original这个原数组从0开始的Math.min(original.length,newLength)个元素拷贝到了copy数组从0开始的n个元素中。完成拷贝工作。
至于为什么Math.min(original.length,newLength)呢,我们可以这么考虑,当newLength>orginal.length时,因为新数组内容要从原数组中拷贝得到,(newLength-original)多出的数组内容就浪费了空间,而当original.length>newLength时,为了保证新数组不会发生OOM,我们自然也要用最小的数了。
下面就进入正题了。我们接下来会先从ArrayList的常用变量讲起,再从它的构造方法下手,然后谈谈它的常用API,增删改查等等。
1、常用变量
//序列化的版本号
private static final long serialVersionUID = 8683452581122892189L;
//默认的数组大小为10
private static final int DEFAULT_CAPACITY = 10;
//空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认构造函数中容量为10的数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//真实存放内容的数组
transient Object[] elementData;
//数组大小
private int size;
很明显我们能得到以下信息:ArrayList内部采用的存储结构是数组,默认容量大小为10,用size记录长度,而elementData就是实际存放元素的数组。这里的空数组和容量为10的数组是为了快速方便的构造而使用的,下面的构造函数就会谈到这点。
2、构造方法
/**默认构造方法,只是初始化容量为10
*构造方法一
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**自定义初始容量
*构造方法二
*/
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);
}
}
/**将实现Collection接口的类对象作为参数初始化数组和size
*构造方法三
*/
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;
}
}
构造方法一是默认的构造方法,这里我们就直接返回DEFAULTCAPACITY_EMPTY_ELEMENTDATA,也就是大小为10的数组
构造方法二我们实例化指定容量的数组,这也没啥好说的。
构造方法三我们把实现了Collection接口的存储着元素的类型和当前实例化出的ArrayList存储的元素类型相同的对象作为参数,我们将它转换为数组类型并用Arrays.copyof()开辟出合适的空间大小的新数组并让elementData指向它。这里有一行注释很有意思,我们来看看:c.toArray might (incorrectly) not return Object[] (see 6260652)。
这里的报错原因是这样的:c.toArray()得到的可能并不是Object[]对象,比如在Arrays中定义的ArrayList中定义的toArray(),它返回的数组就是泛型数组,而不是Object[],因此有下面的实例
Integer[] a = {1,2};
List list = Arrays.asList(a);
System.out.println("转换为List后的类型:"+list.getClass());
Object[] array = list.toArray();
System.out.println("转换回数组后的类型:"+array.getClass());
我们可以看到,即使将它转换为Object[],而实际上它的类型确是Integer[]。这就是上述注释的错误,也是要判断elementData.getClass() != Object[].class的原因
3、CRUD接口
3.1 查
我们在ArrayList中查找效率非常高,就是O(1)的复杂度。
E elementData(int index) {
return (E) elementData[index];
}
public E get(int index) {
rangeCheck(index);//越界检查
return elementData(index); //下标取数据
}
我们在用get()查找时要先做一个越界检查,然后就用数组的下标去查询元素了。
3.2 改
我们对ArrayList中元素的修改并不会导致modCount的修改,modCount只会在改变长度的情况下修改。
public E set(int index, E element) {
rangeCheck(index);//越界检查
E oldValue = elementData(index); //取出元素
elementData[index] = element;//覆盖元素
return oldValue;//返回元素
}
同样地,我们是使用数组下标查找并修改的元素,因此修改效率也很高。
3.3 增
ArrayList中添加元素时需要先用ensureCapacityInternal()和ensureExplicitCapacity()来确认容量是否充足,如果充足就不需要扩容了,直接添加元素即可。如果不足的话就要用grow()方法扩容了。扩容时默认扩容1.5倍数,如果还不够的话就把最小需要容量作为容量。最后再判断容量是否大于了最大容量MAX_ARRAY_SIZE,如果是就执行hugeCapacity(),判断最小需要容量是否大于MAX_ARRAY_SIZE,如果是那么新容量为Integer.MAX_VALUE,否则就为Integer.MAX_VALUE-8。
要注意的是,扩容操作需要调用Arrays.copyof()把原数组整个复制到新的数组中,操作代价很高。所以如果在实际开发中频繁更改的场景下不宜使用ArrayList。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
private void ensureCapacityInternal(int minCapacity) {
//用==判断数组是否使用默认构造函数初始化的
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//如果确定要扩容,就修改modCount
// overflow-conscious code
if (minCapacity - elementData.length > 0)//此处是判断是否扩容关键
grow(minCapacity);
}
//需要扩容的话默认扩容1.5倍,如果还不够,就用目标的size作为扩容的容量。在扩容成功后会修改modCount
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);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
public void add(int index, E element) {
rangeCheckForAdd(index);//越界判断
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);//将index开始的数据后移一位
elementData[index] = element;
size++;
}
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 boolean addAll(int index, Collection extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);//移动(复制)数组
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
3.4 删
删除操作我们没有再去做扩容,所以用不到Arrays.copyof(),相对的经常调用的函数是System.copy()。
删除某一指定位置的元素就是先判断有无越界,再读出删除的值,再调用System.copy()将要删除的元素后面的元素全都前移一位,再将最后一位设为null,给size-1,由GC自动回收空间,最后再返回删除值。
其他删除操作原理类似。
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);//将index+1后numMoved位的元素复制到index后numMoved位 上
elementData[--size] = null; // clear to let GC do its work
//把尾数据置空,让GC负责回收
return oldValue;
}
//根据下标从数组取值 并强转
E elementData(int index) {
return (E) elementData[index];
}
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);//根据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++;//修改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 //置空 不再强引用
}
public boolean removeAll(Collection> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
//批量删除
private boolean batchRemove(Collection> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
//将elementData与Collection交集的元素用后面的元素覆盖替代,而w表示执行这部操作后原来的末尾位置,所以下一步操作直接置空w后的所有元素
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;
}
在此之外,我们在介绍一下其它的API。
4、另外的API
4.1 判空
判空
public boolean isEmpty() {
return size == 0;
}
如上代码所示,判空的复杂度只在O(1)
4.2 包含
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
//用for循环查找值,但要分类o是否为null
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;
}
复杂度是线性的
4.3 清空
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
遍历列表并将所有元素置空,size设为0,然后由GC自动回收空间。
4.4 两个toArray()方法
/**
* 直接copy出Object数组
*/
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
/**
* 运行态时通过传入一个与实例对象存储类型相同的元素,返回一个类型确定的数组
* 如果传入的数组大小不够就直接copy一个新的数组
* 如果大于elementData就将elementData上的内容拷贝到传入的数组上,并将其余元素置空
*/
public T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
两种方式不同:1、public Object[] toArray();方法直接返回Object[],无需传参,使用简单 2、public