一、类的定义
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
类的继承关系:
二、ArrayList的属性
//数据存储在当前数组中,其中 ArrayList的容量就是当前数组的容量,
//默认长度是 DEFAULT_CAPACITY.private transient Object[] elementData;
private int size;//ArrayList的容量,表示 elementData数组中包含的元素的个数。
private static final int DEFAULT_CAPACITY = 10;//默认的长度。
private static final Object[] EMPTY_ELEMENTDATA = {};//默认的空元素
关键是 Object[] ,所有的元素被存储在当前的Obejct的数组中。同时size中记录了当前ArrayList的容量。所有的操作最终都会转变为对这两个对象的操作。这两个对象就是当前类的状态变量。
三、ArrrayList的内部类
//这个是一个私有的内部类
private class Itr implements Iterator<E> {
int cursor; // 下一个返回元素的index。
int lastRet = -1; // 上一个返回元素的index。-1表示没有返回顾。
int expectedModCount = modCount; //这个 modCount是在AbstractList中定义的,表示修改的数量。
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;//将游标+1
return (E) elementData[lastRet = i];//返回对应的元素。
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();//看下元素是否被修改过了
try {
ArrayList.this.remove(lastRet);//删除对应的元素,其中 modCount会被修改
cursor = lastRet;//cursor就需要变为上一个返回的元素。
lastRet = -1; //上一个返回的元素被删除了。
expectedModCount = modCount;//修改一下变更的元素。
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
//检查一下当前的modCount是否被变过了,如果已经被改变了直接抛异常。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
ListItr:提供了一个能够双向访问ArrayList内部元素的内部类。这个类在 Itr的基础上扩展了向前访问的功能。
//ListTtr:提供了一个能够对当前元素进行修改的对象的实现。
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
super();
cursor = index;//可以从当前的数组的中间开始。往前,往后遍历,Itr只能往后。
}
public boolean hasPrevious() {//还有前驱元素
return cursor != 0;
}
public int nextIndex() {//返回下一个元素的下表。
return cursor;
}
public int previousIndex() {//返回签约元素的下标
return cursor - 1;
}
@SuppressWarnings("unchecked")
public E previous() {//返回前面的一个元素。
checkForComodification();
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i;
return (E) elementData[lastRet = i];
}
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
public void add(E e) {
checkForComodification();
try {
int i = cursor;
ArrayList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
四、构造函数
//初始容量的构造函数:创建对应大小的数组:
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
无参构造函数是一个空的数组,这个和之前会构造一个 长度为10的数组的实现是不一样的。
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();//首先转变为一个Array。
size = elementData.length;//直接转变。
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)//如果转变的过程中失败了,就创建一个新的Object[],然后将对应的参数copy进去。
//Arrays方法中使用到了很多native方法,这些方法得到了虚拟机的额外的支持。包括 newInstance,和arraycopy 方法。
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
四、ArrayList的 关键方法
1) 写入数据
写入数据主要是添加元素到当前Object[]队列当中,这个过程中有一个关键是当添加的数据太多的时候,是如何扩容的。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
首先调用了一个ensureCapacityInternal(size +1 );确保容量是够的。
看下代码:
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {//如果当前的队列是一次
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);//根据minCapacity来扩展当前的 数组的长度
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//先修改 modCount
// overflow-conscious code
if (minCapacity - elementData.length > 0)//如果需要的长度比 数组的长度大,就需要调用grow的方法。
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//首先是尝试 扩大 到1.5倍。
if (newCapacity - minCapacity < 0) //如果还不够,就使用 指定的大小
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)//如果当前的大小比 最大的MAX_ARRAY_SIZE还大,那么就使用 Integer.MAX最大的。
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//然后开始cop数据
}
如何插入一个元素,首先是进行容量,如果当前的ArrayList是刚开始创建的话,就创建一个Math.max(DEFAULT_CAPACITY, minCapacity);容量的数组。然后开始判断容量。如果当前数组能够放得下对应的元素话,就返回,首先尝试一下1.5倍够不够,不够就按照需要的空间来扩。然后看下 ARRAY_MAX_SIZE哪个更大,如果不够,那么就使用Integer.MAX来扩容。
//将element添加到指定的 位置index上。
public void add(int index, E element) {
rangeCheckForAdd(index);//判断Index。在size以内
ensureCapacityInternal(size + 1); // Increments modCount!! 确定是否需要扩容。
System.arraycopy(elementData, index, elementData, index + 1,
size - index);//将所有的数据往后copy一个。
elementData[index] = element;//写入数据。
size++;//将array的大小增加一个。
}
//添加所有的 数组
把 容器 c 写到 index后面的数组中,
public boolean addAll(int index, Collection<? extends E> c) {//将Collection 添加到 当前数组中。
rangeCheckForAdd(index);//手下判断当前de
boolean modified = false;
for (E e : c) { //依次将当前序列中的数据写到 新的队列中来。
add(index++, e);
modified = true;
}
return modified;
}
2)读取数据
//获取 index位置上的 元素
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
3)遍历数据
// 返回一个Iterator实例Itr,这个实例是当前对象的匿名内部类。
//能够通过当前的 Iterator来访问。这个当中涉及到内部类和 迭代器模式的内容,后期单独讲。
public Iterator<E> iterator() {
return new Itr();
}
//将当前 List转变为一个数组。这个过程中,如果接收的数组,就会创建一个新的数组,将当前内存中的数组copy进去。
//如果接受的数组的大小大于 List的大小, 需要将接受数组中,array大小的下一位的值设置为null值。
public <T> 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;
}
如果 数组a 空间不够,创建一个新的数组来放数据。如果空间够的话,就将当前的数据拷贝到 a中,将最后一个位置设置为null。后面的数据不变。
例如
ArrayList list = {"0","1"};
String[5] a = {"a","b","c","d","e"};
list.toArray(a).
结果, a= {"0","1",null,"d","e"}
4)删除数据
//删除所有的内容
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;//将当前的对象中的所有的字段都变为 null,当前List中的值变为 null。
size = 0;
}
//删除index位置上的某个参数
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;
}
首先找出需要移动位置的数据的个数,然后将index后的所有数据往前移动一个,最后一步,将size减一,
然后把最后一个位置上的值 设置为null。这一步很关键,避免内存泄露。
//删除某个值,这个当中需要对null和非null进行分开处理。
public boolean remove(Object o) {
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])) {//如果当前的对象不为null使用 equals更加的合适。
fastRemove(index);
return true;
}
}
return false;//当前系统中根本就不存在当前的对象。
}
//批量删除
public boolean retainAll(Collection<?> 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[r]是需要留下来的元素。
elementData[w++] = elementData[r]; // 表示elementData[w] 中是所有不会被删除的元素,这些元素都会被移动元素的最左边。和GC中的标记-整理算法的实现相类似。
} 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;
}
批量删除和GC中的标记整理算法有点相似,在gc中 标记和清理是分为两个步骤做的,但是在这里一步做了。
5)其他方法
/**
*找出当前元素在ArrayList中的位置。
*其中null值和 非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;
}
总结:对于ArrayList中相关的 remove中相关的方法,一旦某一个位置上的元素被删除后,这个时候需要将对应的位置 设置为 elementData[index] = null.这个是最典型的防止内存泄露的方式。