ArrayList 数组容器类,实现了List,RandomAccess,Cloneable,Serializable四个接口,继承自AbstractList,具有动态扩容,随机访问,可复制,可序列化四个主要功能。
源码分析
核心
之前文章分析过这个接口所需要实现的方法,ArrayList 实现的核心是两个属性
transient Object[] elementData; // 元素载体
private int size; //容器存放的元素大小
elementData 数组用于存放容器,transient 表明容器内的元素不能被序列化。这时候有些同学就表示疑问了,类声明的时候明明是实现了Serializable接口的,表明ArrayList是能够序列化的,此处为什么又要使用transient关键字呢?ArrayList到底能不能被序列化呢?
这里先说结论 ArrayList 是能被序列化的,有兴趣的同学可以做个实验,后面在回顾基础的时候会专门对序列化进行分析。
扩容
ArrayList 有三个构造函数
ArrayList(int initialCapacity) //指明容器大小
ArrayList() // 默认容器初始化大小
ArrayList(Collection extends E> c) // 通过Collection构建容器
这里讲一下第二个构造函数,这个函数只做了一件事,赋空数组。有很多资料说ArrayList 构造函数如果不指定大小,默认是10,这种说法是不严谨的。默认初始化后的大小其实是0,第一次扩容大小为10。
扩容主要发生在Add操作时,每次add 都会检查容量是否够放入新的元素(ensureCapacityInternal方法),如果不够,比较得出一个逻辑最小预期容量(calculateCapacity方法),然后将容量扩至原来的1.5倍(grow方法),如果还是不能满足需求,则将容量扩充至预期容量。
//入口方法,每次add时先检查 size+1
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//如果是默认空数组,返回默认容量和minCapacity中较大的值
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
//增加修改次数
modCount++;
// 如果预期容量比实际容量大,则需要扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//尝试扩容至原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
//如果1.5倍还不够,则扩容至预期容量
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
//如果预期容量比最大数组容量还大
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
增
增加的方法有很多,这里重点讲一下增加方法中带有下标的方法,比如add(int index, E element)
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));
}
其中要注意的是rangeCheckForAdd(index)检查,插入的下标不能大于数组已有元素大小,否则会报数组越界异常。
删
删除有两种主要的删除方式,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) {
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;
}
删除主要是通过遍历找到元素,然后通过平移覆盖元素,最后将尾部元素置为null,等待垃圾回收。这其中有一点需要注意,如果你的元素是int类型,在删除的时候一定要小心。很多人只知道remove(Object o)一种删除,想当然的以为remove(12) 会删除元素为12的元素,其实真正执行的是删除index为12的元素,这是就会参数数组越界问题。
查
List提供了get(index)方法让我们更快的查询数组中元素,我们平时用的最多的是这样的语句:
for(String s: arrays){
}
这是1.5之后提供的foreach语法,他其实是一种语法糖,通过idea工具反编译class文件(或者javap -verbose命令)
List list = Arrays.asList(Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4), Integer.valueOf(55));
Iterator var2 = list.iterator();
while(var2.hasNext()) {
Integer a = (Integer)var2.next();
System.out.println(a);
}
可以看到它经过编译器翻译后,底层使用的是iterator迭代器原理。既然使用的是迭代器原理,首先想到便是Fast-Fail机制。也就是说你不能在循环中删除元素,否则会报ConcurrentModificationException异常。
iterator
arrayList内部类Itr 实现了Iterator接口,这个接口主要有三个方法
- boolean hasNext() //是否还有下一个元素
- E next() // 获取下一个元素
- E remove() //删除当前元素
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;
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();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
可以看到Iterator通过modCount 和 expectedModCount 来实现 FastFail机制,每次对arraylist的操作都会修改modCount,如果在迭代的过程中ArrayList被修改就会触发快速失败。如果单线程对ArrayList进行删除,可以使用Iterator.remove() 方法,一般不建议在循环的时候删除元素。
ListIterator
Iterator的拓展接口,提供了双向遍历的能力。一般的Iterator是从前向后遍历,ListIterator功能更加全面一点。
subList
ArrayList中有一个方法
public List subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
返回的是指定区间的子列表,SubList是一个内部类,举一个简单的内部方法
public E set(int index, E e) {
rangeCheck(index);
checkForComodification();
E oldValue = ArrayList.this.elementData(offset + index);
ArrayList.this.elementData[offset + index] = e;
return oldValue;
}
虽然他是new出来的对象,但可以看到他操作的是原列表的值,相当于原列表的一个视图,所以使用的时候要慎重。
使用规范
阿里的Java操作手册中有如下几点强制规范(这里引用一下):
- 【强制】ArrayList的subList结果不可强转成ArrayList。
- 【强制】在 subList 场景中,高度注意对原集合元素的增加或删除,均会导致子列表的遍历、 增加、删除产生 ConcurrentModificationException 异常。
- 【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全 一样的数组,大小就是 list.size()。
- 【强制】使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法。
- 【推荐】集合初始化时,指定集合初始值大小。