ArrayList是我们最常用的集合框架,可以说没有之一,接下来阅读一下一下ArrayList的源码
ArrayList继承自 AbstractList,实现了List,RandomAccess,Cloneable,Serializable(java.io)。JAVA类讲究见名知意。光看名字就能猜出来ArrayList底层是以数组实现的。所以ArrayList一定是有序的,但是存在重复数据。观看源码我们可以看出来,它是线程不安全的(因为通篇源码都没有synchronized和asynchronized关键字)
接下来放个截图,是ArrayList的所有方法和成员变量,内部类等
用来进行java序列化和反序列化的,没啥好说的(什么?你问我什么叫序列化?自己百度去,我特么怎么知道)
我们看一下这个属性的定义
private static final int DEFAULT_CAPACITY = 10;
作为一个合格的java码农(文档翻译官),我们解读一下这条属性的含义。我们都知道ArrayList的底层是个数组,我们还知道java集合框架内的元素数量是可变的,可是数组不可变,那么JDK在制作ArrayList的时候,一定要给他底层的数组(我们一会就能看到它)一个默认的长度,那么,这个属性的作用就是默认的长度。如果元素个数发生变化,那又是另一个故事了,以及当你使用一下代码
package com.study.basicJava.sourceCode;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List list = new ArrayList<>();
System.out.println(list.size());
}
}
运行的结果为0,那么又是另一个故事了。具体我们可以等到ArrayList的构造方法里面详细研究,我们暂且记下来这个属性
先看声明
private static final Object[] EMPTY_ELEMENTDATA = {};
注释翻译过来就是用来为空的ArrayList共享的数组,这句话说起来有点拗口,但是 我争取给大家解释清楚到底是怎么一回事。首先我们看这个EMPTY_ELEMENTDATA是用static关键字修饰的,那么说明,在整个ArrayList对象的生命周期内,它是一直不变的,也就是一直都是一个空数组。当你创造出一个空的ArrayList时,空的ArrayList用来存储元素的数组就是这个狗东西,但是一旦你向这个空的ArrayList中插入元素时,ArrayList就不使用它了。具体可以往后看。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
好,我们继续翻译注释。用来为确定大小的空的Arraylist实例共享的数组。这个数组的作用是用来和EMPTY_ELEMENTDATA区分当第一个元素被插入到ArrayList中时,我们应该为ArrayList扩充多大的空间。。。。。这个解释起来比较拗口,我们统一都放到构造方法里讲吧。大家就记住一点,当代码为
List list = new ArrayList()<>
时,起作用的是EMPTY_ELEMENTDATA;、
当代码为
List list =new ArrayList<>(10)
时,起作用的就是我们的 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
-----------------------------------------未完待续--------------------------------------------------------------------------------------------------------------------
代码先上
transient Object[] elementData;
在这行代码的后面,还有一句话,我觉得很有意思,也给大家写出来
// non-private to simplify nested class access
我们先看最后一句话,翻译过来就是,没有使用private修饰,以便简化嵌套类访问(很有意思,不是吗?)
先翻译一下注释:存储ArrayList元素的数组缓冲区,ArrayList的容量等于此数组的长度。任何一个空ArrayList都满足 elementData == DEFAULICAPACITY_EMPTY_ELEMENTDATA,当向这个空的ArrayList中插入第一个元素时,ArrayList的容量将会被扩充到DEFAULT_CAPACITY。
这一段话都很好理解,不好理解的讲到构造函数的时候也就好理解了,而我比较好奇的地方在于两个方面
1 为什么elementData没有用private之类的修饰符控制?
2 transient关键字的作用是什么?为什么要用transient关键字修饰?
因为本菜鸡是一边看源码,一遍写的这篇博客,所以,先记下来这两个问题,等下再研究。而且,我认为,特备是第二个问题,很有必要专门开一篇博客
代码
private int size;
一句话:当前ArrayList的容量,或者是当前ArrayList中元素的数量
现在进入构造函数部分
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);
}
}
注释里面写很清楚,构造一个指定容量的空ArrayList
参数 initialCapacity代表指定的容量大小,类型为int。需要注意的是,当参数initialCapacity为负数时,会抛出IllegalArgumentException
代码很好理解,大意就是当initialCapacity大于0时,用来存放元素的数组elementData被初始化,或者被赋值为一个长度为initialCapacity的Object数组。如果initialCapacity等于0的时候呢,elementData会被赋值为EMPTY_ELEMENTDATA。我们仍然记得,EMPTY_ELEMENTDATA是一个长度为0的Object静态static数组,为什么会将EMPTY_ELEMENTDATA赋值给elementData呢?我们可以这么理解,initialCapacity等于0,说明此时创造的ArrayList数组是个空数组,至少在此时,使用者没有发生或者没有现象预示即将发生对这个ArrayList的扩充行为(向ArrayList插入元素),我们用它来标识这个ArrayList是个空的,而且不会向里面插入元素
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
构造一个初始容量为10的ArrayList,但是里面没有元素;
在这个构造方法中并没有体现初始容量为10,只是将DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值给elementData,标识此时的ArrayList虽然是空的,但是会插入元素。
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;
}
}
构造包含指定集合对象的元素的ArrayList,按集合对象的迭代器返回元素的顺序排列。
参数是指定的集合对象(Coolection对象都可以,但是如果ArrayList有泛型的话,只能放同样泛型的Coolection对象)
当指定的集合对象为null时,会抛出NPE异常
代码逻辑也很好理解,首先将指定集合对象C转换成数组赋值给elementData,再计算出此时ArrayList的容量,如果容量为0时,则将EMPTY_ELEMENTDATA赋值给elementData,如果容量不为0,那么此时还需要判断elementData是不是一个Object类型的数组,如果不是,就需要将elementData转换成Object数组。在这里,JDK使用了一个工具类Arrays的copy方法,这个方法的作用是将一个数组对象转换为指定大小,指定类型的数组对象,还是很有意思的,有机会可以好好研究一下
接下来就是ArrayList的各个方法了
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
我们先不看具体这个方法是如何实现的,我想和大家一起,根据这个方法的名称去猜测这个方法到底是干啥的。trim,根据有道词典,意思为修剪,去除的意思。在java中呢,一般也是这个意思,比如,String就有一个trim()方法,作用是去除字符串内所有的空格。那么,我们大胆的猜测,trimToSize()的作用是将ArrayList的容量修剪为存储在当前ArrayList内拥有的元素数量。胡扯完毕,看注释
将ArrayList实例的容量缩减至ArrayList此时拥有的元素数量,应用程序可以使用此操作将ArrayList占有的内存得以最小化。
看来,我们才对了。
然后,我们看方法实现。这时候,我发现一个有意思的变量 modCount。我并没有在ArrayList的源码中看到这个变量的声明,看来,我们应该去ArrayList他爹,AbstractList中找找,不负众望,我找到这个鳖孙了
protected transient int modCount = 0;
这么长的一段话呢,看了就头皮发麻,咱们主要的目的就研究ArrayList,不是研究他爹,我们说一说这个属性是干嘛的就行了。
这个属性在ArrayList中的作用主要是为了记录ArrayList中存储的元素增删改的次数,也可以说是元素数量变化的次数。当然,他还一个别的作用,我们暂时研究不到这个地步,不过说一说也无妨。根据注释解释的来看,他还记录了一种状况,那就是当ArrayList使用迭代器的时候,由于某些操作,使迭代后的结果和我们期望的结果不同。说白了,就当多线程同时工作时引起的ArrayList(所有继承了abstractlist的类都会有这种情况)数据不同步。这些是一些线程安全问题,暂时先不讨论
接下来的这个方法是用来保证或者说扩充ArrayList的容量的
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);
}
}
方法内先计算最小扩充量,以保证能够放下参数minCapacity所代表的容量。计算规则比较简单。首先要看存储数据的elementData是否是一个空数组(DEFAULTCAPACITY_EMPTY_ELEMENTDATA),如果是的话,最小扩充量就为0.如果不是的,最小扩充量就是10(DEFAULT_CAPACITY)
之后在比较一下最小扩充量和参数的大小。如果参数大于我们计算出的最下扩充量,说明我们确实需要扩容,因此,调用另一个方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
既然选择扩容了,那么集合内的元素数量是肯定会发生变化,因为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);
}
这个方法才是真正保证集合的容量能够大于等于参数。先记录之前的元素数量,接下来我们看到一个移位运算,且不管这个移位运算最终的结果如何,但是我们会发现,新的容量一定是大于旧的容量的。接下来的逻辑就简单了。如果新容量小于参数,那么就将参数赋值给新容量。
然后重置elementData。
还有几个方法也是用来计算容量相关的,我们来看一下
容量计算
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
逻辑很好理解,这里不再赘述。
接下来这个方法的意义没看太懂,先记录一下
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
返回集合内元素的数量
public int size() {
return size;
}
判断集合内是否有元素
public boolean isEmpty() {
return size == 0;
}
判断集合内是否含有参数元素
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
集合拷贝方法(浅拷贝,不会复制元素本身)
public Object clone() {
try {
ArrayList> v = (ArrayList>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
浅拷贝最大的特点就是,假设我们的源集合对象是list,复制得到的集合对象是copyList,那么
list.equals(copyList)
的返回值为false。
数组转换方法
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
这个方法按照JDK注释来讲,其实是数组和集合之间的桥梁,允许集合转数组。
在ArrayList中,这个方法的返回值是一个新分配的数组,它包含ArrayList内的所有元素,元素顺序和ArrayList中的元素顺序一致。而且,由于返回值是新分配的,所以我们可以对这个数组做出更改操作(添加或移除元素)
另一个带泛型的集合转数组方法(推荐使用)
这个方法带一个入参,就是一个泛型数组。这个方法的精妙之处在于返回数组的处理。如果参数a的长度比集合内元素的数量小的话,返回值是一个长度为集合元素数量的泛型数组。反之则会将集合内的元素填充到数组a中,然后a剩下的位置将会以null填充。
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;
}
根据下标获取元素的一个方法。由于缺省了修饰符,所以这个方法只能在本类和同一包下的其他类内使用。
众所周知,我们的ArrayList是以数组为底层实现的,那么获取某个位置的元素,其实也就是获取elementData数组中某个位置的元素。
E elementData(int index) {
return (E) elementData[index];
}
接下来是暴露给外部的获取元素的方式,参数也是下标。当然,首先还是要检查一下元素下标是否超过元素数量
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
元素替换
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
在集合尾部添加元素。首先先对集合进行扩容,也就是说得判断elementData的长度是否还够用,不够用就要重新申请一个新数组,具体逻辑就在前面。
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++;
}
移除集合中某个位置的元素
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;
}
真正的原理就是从让这个位置往后的元素统一向前移动一位,然后申请一个新的数组盛放新的元素。我们需要注意的是移除元素的方法会先检查参数的合法性,并且会返回被删除的元素。
还有一个快速删除的方法,原理都一样,但是不会检查参数和返回删除元素
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
}
这是一个私有方法,具体作用在于我们接下来要介绍的一个方法,删除特定元素
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;
}
原理和contains类似,如果集合内有这个元素就移除,没有就返回false。
清除集合
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
将某个集合被的全部元素添加到ArrayList中,第一个参数是指插入的位置
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;
}
逻辑也很好理解,仍然是重新分配数组,利用toArray方法将参数集合C转换为数组然后与elementData合并。
移除集合内一段区间内的元素
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
size = newSize;
}
我们注意到这个方法是使用protected修饰的,说明它只在本类和同包下使用。
首先对modCount进行自增操作,表示集合内的元素数量发生了改变;然后计算需要移除的元素数量。在之前,我们说过System.arraycopy方法的含义,在这里我们重新说一下
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
从src数组中下标为srcPos的位置开始,向后复制length个元素,然后从dest数组的下标为destPos位置开始粘贴。
继续来看我们的方法。那么这个操作的含义就是将elementData中从下标为toIndex位置开始复制numMoved个元素,然后从elementData的下标为fromIndex的位置开始粘贴。
计算出移除后集合元素的新数量newSize(总数量减去移除的数量就是剩下的数量),然后将移除过的位置统一赋值为null,通知GC回收
移除交集
public boolean removeAll(Collection> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
移除参数C集合内的所有元素,但是有个要求,集合C不能为null。集合C可以是当前ArrayList的子集,也可以不是,甚至可以没有交集。
但是我们可以根据返回值看出来。如果当前集合和参数c有交集就会返回true,然后交集会在当前集合中被移除;否则返回false。
移除差集
public boolean retainAll(Collection> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}
按照注释的说法是只保留集合内与C相同的元素,说白了就是保留交集,移除差集。
如果当前List不是集合C的一个子集,则返回true。否则返回false。
或者调用此方法后,当前集合发生了改变就返回true,反之false。
批量删除
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;
}
刚才的removeAll和retainAll的具体实现都是靠这个方法。通过传递complement来决定到底移除那些元素,删除方式和removeRange方法基本一样
接下来看一看ArrayList的几个内部类。
第一个,Itr,据说是AbstractList.Itr的优化版
private class Itr implements Iterator {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
基本和AbstractList.Itr一样唯一不同的是,AbstarctList.Itr获取数据用的是get,但是在ArrayList中进行了更确切的实现,就是直接获取数组中的元素。
至此,ArrayList基本的东西都扯完了。由于本楼主也是个菜鸟,这篇博客其实就是把JDK文档翻译了一遍,一些难懂的知识基本也没设计(序列化等),有什么不对的地方希望大家给我指出来,谢谢