回顾下集合体系中,ArrayList
的一些主要操作的原理,这里查看的Android中的java运行时的源代码,和标准JDK中的实现有一定差别,主要去掉了一些范围检测,提高了一定的使用性能。
成员变量
首先看下成员变量:
//如果不给定ArrayList的初始容量,那么默认为10
private static final int DEFAULT_CAPACITY = 10;
//如果初始化容量设置为0,那么使用这个作为初始化数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//如果不设置初始化容量,那么使用这个作为初始化数组,和EMPTY_ELEMENTDATA 的区别是为了
//在第一次添加元素的时候以便知道加入的元素的数量
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存储元素的数组
transient Object[] elementData; // non-private to simplify nested class access
//ArrayList 包含的元素数量
private int size;
构造方法
//不传入任何参数的构造
public ArrayList() {
//执行默认的初始化
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//传入初始化容量的构造
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//大于0,就使用传入的参数直接初始化数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//如果传入了0,使用EMPTY_ELEMENTDATA进行初始化
this.elementData = EMPTY_ELEMENTDATA;
} else {//不能传入小于0的数,否则抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
//传入集合的构造
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();//转换为数组赋值给elementData
if ((size = elementData.length) != 0) {//判断数组是否有内容
// 判断Class对象是否为Object[].class
if (elementData.getClass() != Object[].class)
//不是的话,复制一个新的赋值给elementData
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 如果传递的集合是空集合,那么使用EMPTY_ELEMENTDATA初始化
this.elementData = EMPTY_ELEMENTDATA;
}
}
add方法
//将指定的元素添加到队尾
public boolean add(E e) {
//进行可能的扩容操作
ensureCapacityInternal(size + 1);
//直接将元素添加到数组末尾
elementData[size++] = e;
return true;
}
//扩容操作
private void ensureCapacityInternal(int minCapacity) {
//如果没有使用初始化容量的构造方法,默认就是这个DEFAULTCAPACITY_EMPTY_ELEMENTDATA
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//判断DEFAULT_CAPACITY(默认10个)和传入的容量,哪一个大使用哪一个扩容
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//进一步确认
ensureExplicitCapacity(minCapacity);
}
//确认容量并扩容
private void ensureExplicitCapacity(int minCapacity) {
//添加或者移除会引起这个数字自增,理解为List的修改次数
modCount++;
// 判断传入的容量大于elementData的长度才进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//核心扩容方法
private void grow(int minCapacity) {
// 取出原始elementData的长度
int oldCapacity = elementData.length;
//进行1.5倍扩容
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果新容量比当前需求容量小,直接将当前容量赋值给newCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果newCapacity超过MAX_ARRAY_SIZE,那么将扩容到Integer.MAX_VALUE
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 使用指定容量扩容数组,返回一个新的扩容数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
//返回容量上限,最大Integer.MAX_VALUE
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
总之add
方法在elementData
容量不够的情况下会进行扩容,如果初始化的10个容量不够的话,进行1.5
倍扩容,如果还不够直接使用size+1
,然后进行了容量上限的检查,最大支持Integer.MAX_VALUE
个元素存储,最后使用Arrays.copyOf()
方法进行数组复制。
看下两个参数的add
方法:
//在指定位置插入指定的元素
public void add(int index, E element) {
//索引大于size或者索引小于0,直接抛出异常
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
//扩容操作
ensureCapacityInternal(size + 1); // Increments modCount!!
//原数组和目标数组都是elementData,因此是在原数组上进行扩容,将index后面的
//元素挪动一位,这样index位置就可以插入新添加的元素了
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//在index位置上插入元素
elementData[index] = element;
size++;//size增加
}
看看addAll()
方法:
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();//转换为数组
int numNew = a.length;//获取长度
ensureCapacityInternal(size + numNew); // 扩容
//把需要插入的数组复制进elementData尾部
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;//size增长
return numNew != 0;//返回插入结果
}
在指定位置插入集合的addAll()
方法:
public boolean addAll(int index, Collection<? extends E> c) {
if (index > size || index < 0)//不合理就抛出异常
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
//同样是获取插入的长度,预先进行扩容操作
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
//需要将index后面的元素全部向后移动,计算下index后面的元素数量
int numMoved = size - index;
if (numMoved > 0)
//从index移动到index + numNew,预留出需要插入的集合数量
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
//移动完成后插入新的元素数组
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;//size增长
return numNew != 0;//返回
}
get方法
public E get(int index) {
if (index >= size)//判断索引是否合法
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
//合法的话返回elementData索引下标的对象
return (E) elementData[index];
}
set方法
在指定位置设置制定元素:
public E set(int index, E element) {
if (index >= size)//检测索引合法性
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
//找到指定位置的元素
E oldValue = (E) elementData[index];
//更新指定的元素
elementData[index] = element;
return oldValue;//返回旧元素
}
remove方法
首先看看移除指定位置的方法:
//移除指定下标的元素,并返回移除的元素
public E remove(int index) {
if (index >= size)//检查合法性,不合法抛出异常
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
modCount++;//修改标识自增
E oldValue = (E) elementData[index];//取出数组的index位置的元素
//确定需要进行移动的index后面的元素数量
int numMoved = size - index - 1;
if (numMoved > 0)
//将index后面的元素向前移动一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//size自减,并将最后一位赋值为null,GC会负责回收
elementData[--size] = null; // clear to let GC do its work
return oldValue;//返回移除的元素
}
移除指定元素的方法:
public boolean remove(Object o) {
if (o == null) {
//是null的话循环找出第一个是null的对象,移除后直接返回
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
//移除操作
fastRemove(index);
return true;
}
} else {
//不是null循环数组,找出数组中第一个相同的对象,然后移除返回
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;//确定index后面需要移动的元素数量
if (numMoved > 0)
//同样将元素向前移动一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//size自减,将最后的元素置Null,等待垃圾回收
elementData[--size] = null; // clear to let GC do its work
}
移除指定集合的方法:
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);//如果传入的集合是Null,直接抛出空指针异常
return batchRemove(c, 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++)
//循环数组,把不包含的对象随着w自增,全部移动到前面
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {//上面r循环一遍后是等于size的所以不走这里
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {//如果w==size说明没有包含的对象,只有不包含才走这里
// clear to let GC do its work
//这样从w索引开始后面的所有的都是多余的了,置Null,等待垃圾回收
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;//包含几个就自增几个标识
size = w;//w赋值给size
modified = true;说明修改成功置为true
}
}
return modified;//返回
}
相反的一个方法,保留传入的集合中的所包含的对象:
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);//判断是否为null
return batchRemove(c, true);//这里不同是传入了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 {
if (r != size) {//这里不会走到
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {//w其实就是包含的元素个数
// clear to let GC do its work
//依旧是把w索引的后面的值置为null,等待垃圾回收
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;//同样包含几个元素就自增几个标识
size = w;//w赋值给size
modified = true;说明修改成功置为true
}
}
return modified;
}
size方法
public int size() {
return size;//直接返回size
}
isEmpty方法
public boolean isEmpty() {
return size == 0;//判断size是否为0
}
indexOf方法
查找指定对象的首次出现的索引位置:
public int indexOf(Object o) {
if (o == null) {
//传入对象为Null的话,就找出第一个元素为null的索引返回
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
//传入对象不为Null的话,就找出第一个和传入对象相同的元素的索引返回
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;//找不到返回-1
}
lastIndexOf方法
查找指定对象的最后一次出现的索引位置:
public int lastIndexOf(Object o) {
if (o == null) {
//传入对象为Null的话,从尾部开始查找,就找出第一个元素为null的索引返回
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
//传入对象不为Null的话,从尾部开始查找,就找出第一个和传入对象相同的元素的索引返回
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
contain方法
public boolean contains(Object o) {
return indexOf(o) >= 0;//实际调用了indexOf方法通过索引来判断的
}
clear方法
public void clear() {
modCount++;//修改标识自增
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;//所有的元素置Null,使用GC回收
size = 0;//size恢复为0
}
其他
还有一些其他的方法这里不再分析。
- 本质上
ArrayList
使用数组进行元素的存储; - 实现了RandomAccess标记接口,可以通过下标进行快速的随机访问;
- 实现了Cloneable接口,表明能被克隆;
- 实现了Serializable接口,支持序列化;
- 添加移除元素等操作可能会引发数组长度的变化;
- 使用
Iterator
循环时候,ArrayList
不能进行add
,remove
,clear
或者其他修改其容量操作,会引发ConcurrentModificationException
异常; ArrayList
不是线程安全的,Vector
是线程安全的,可以使用Collections.synchronizedList()
方法来获取一个线程安全的List
;