1.继承体系
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
2.构造方法
transient Object[] elementData; //底层用Object数组存储元素
//这里定义两个空数组主要是为了区分当前对象初始化的构造方式
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//无参构造初始化用到
private static final Object[] EMPTY_ELEMENTDATA = {}; //有参构造参数为0时用到
private static final int DEFAULT_CAPACITY = 10; //常量
private int size; //记录元素个数
//无参构造
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);
}
}
//注意,有参构造传入0的初始化和无参构造的初始化,是用的两个不同变量的空数组初始化。
3.常用方法
3.1add(E e) 添加元素
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e; //这里是先赋值,后++
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
/*计算并返回需要扩容到的最小容量大小
如果是无参构造初始化的,且这是第一次调用add方法,则最小容量为默认容量DEFAULT_CAPACITY=10
否则最小容量为当前的元素个数size+1
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//条件满足,说明当前对象使用的是无参构造初始化的,且还未进行添加操作
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //这个变量是父类abstractList里面的,用于记录操作次数。
// overflow-conscious code
if (minCapacity - elementData.length > 0) //判断是否需要扩容
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)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//将当前数组复制到扩容后的新数组中
}
ArrayList扩容总结:
当调用add方法后,会调用ensureCapacityInternal(size+1),该方法中首先通过调用calculateCapacity方法计算扩容到的容量大小, 然后调用calculateCapacity方法判断是否需要扩容,需要则最后通过grow方法进行扩容。
如果使用无参构造创建的集合,且第一次调用add方法,则会扩容到10,除此之外的其他情况都会用先使用最小需要的容量size+1和当前容量elementData.length进行对比来判断是否需要扩容,前者大则进行扩容,扩容的规则就是先扩容到1.5倍,如果还是不够,则直接补充到需要的容量。
jdk7的区别:
jdk7在如果调用无参构造方法的时候会自动调用有参构造方法并且参数为10,直接创建一个长度为10的数组,也就是ArrayList对象一创建,初始容量就为10.
jdk8改进为在第一次添加的时候将容量扩充为10。
3.2 add(int index) 在指定位置添加元素
//在指定位置添加元素
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));
}
3.2 get(int index)
//获取指定下标位置的元素
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
//检查下标合法性
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//返回指定所有位置的元素,该方法是default,不能在其他包中被访问到
E elementData(int index) {
return (E) elementData[index];
}
3.3 remove(int index) 移除指定索引的元素 和 remove(Object o)移除指定元素
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;
}
//移除指定元素
public boolean remove(Object o) {
if (o == null) { //ArrayList可以存储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;
}
//这个方法和remove的区别:1.不检查下标2.不返回被删除的元素
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
}
3.4 clear()
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null; //将数组中的所有元素置空,让垃圾回收器回收
size = 0;
}
3.5 addAll(Collection<? extends E> c) 将一个集合添加到当前集合中
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; //c集合的长度为0时,返回false
}
//在指定位置处插入一个集合
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;
}
4.迭代器
ArrayList内部有两个迭代器的内部实现类Itr和ListItr 分别实现了Iterator和ListIterator两个接口,Iterator接口继承了ListIterator接口
4.1 ArrayList中I的Itr类
private class Itr implements Iterator<E> {
int cursor; // 定义一个光标指向当前访问元素的下一个元素(检测光标)
int lastRet = -1; // 定义一个光标指向当前正在访问的元素 (访问光标)
int expectedModCount = modCount; //预期的修改次数默认等于AbstractList中的修改次数modCound
Itr() {}
public boolean hasNext() { //检测后面是否还有元素存在
return cursor != size; //如果检测光标已经到达size处,说明后面已经没元素了,返回false
}
@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]; //访问光标移动到检测光标之间的位置(检测过的)并返回指向的对象
}
final void checkForComodification() {
if (modCount != expectedModCount) //如果期望操作次数不等于实际操作次数,则抛出异常:并发修改异常
throw new ConcurrentModificationException();
}
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();
}
}
}
总结:next方法是迭代器中访问元素的方法,它内部是先移动两个光标,再返回访问光标对应的元素,hasnext是判断当前检测光标的位置是否有元素。remove方法是直接移除访问光标对应的元素(如果该光标位置合法),然后将访问光标置为-1,一般的迭代器遍历元素的格式为:
while(lit.hasNext()){
String s=lit.next(); //这里是遍历字符串元素
System.out.println(s);
}
并发修改异常:ConcurrentModificationException
ArrayList<String> arr=new ArrayList<String>();
arr.add("hello");
arr.add("world");
arr.add("java");
Iterator<String> lit=arr.iterator();
while(lit.hasNext()){
String s=lit.next();
System.out.println(s);
if("world".equals(s)){
arr.add("javaee");
}
}
上面代码运行结果:
Iiterator迭代器可能抛出并发修改异常的地方有两个:next 和 remove方法,造成并发修改异常的根本原因是 modCount != expectedModCount,迭代器的expectedModeCount一开始初始化是等于modCount的,但是在调用next或者remove方法之前,如果调用了ArrayList本身提供的add或者remove,clear等增加或者删除元素的方法(这些方法都会将modCount++),则next方法和remove方法中的checkForComodification()方法会检查到modCount != expectedModCount,因为迭代器里面的expectedModeCount并没有同步增加,所以抛出并发修改异常。
ArrayList<String> arr=new ArrayList<String>();
arr.add("hello");
arr.add("world");
arr.add("java");
Iterator<String> lit=arr.iterator(); //expectedModCount==ModCount=0
arr.add("javaee"); //ModCount++ =1
lit.next(); //expectedModCount!=ModCount 判出异常
ArrayList<String> arr=new ArrayList<String>();
arr.add("hello");
arr.add("world");
arr.add("java");
Iterator<String> lit=arr.iterator(); //expectedModCount==ModCount=0
lit.next();
arr.add("javaee");//ModCount++ =1;
lit.remove(); //expectedModCount!=ModCount 抛出异常
但是如果remove方法检查的时候没有抛出异常,则执行删除动作后,会将expectedModCount=ModCount,所以Iterator中提供的remove方法不会成为并发修改异常的原因,因为他同步了expectedModCount 所以下面的代码不会抛出异常。
ArrayList<String> arr=new ArrayList<String>();
arr.add("hello");
arr.add("world");
arr.add("java");
Iterator<String> lit=arr.iterator(); //expectedModCount==ModCount=0
lit.next(); //expectedModCount==ModCount=0
lit.remove(); //expectedModCount==ModCount=0
lit.next(); //expectedModCount==ModCount=0
总结:Iterator中的remove和next方法会在执行前检测expectedModCount和ModCount是否相等,不相等抛出并发修改异常,而导致他们两个不相等的原因是ArrayList中提供的remove和add以及clear等增删方法会将ModCount++,但是迭代器里面的remove删除方法并不会导致ModCount++,反而还会同步expectedModCount的值。如果非要在迭代器遍历的时候修改元素,那么可以使用ListItr里面的add()方法,该迭代器里面的add方法不会增加ModCount而且会同步expectedModCount