ArrayList是常用的集合.平时用的时候做了些笔记。
一:首先看下ArrayList的继承基础:
public class ArrayList extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable
ArrayList继承了AbstractList.实现了List.相当于动态数组,提供了增加,修改,删除,遍历功能。
实现了RandomAccess.提供了随机访问的功能。即get(i)方法。
实现了Cloneable,即可以被克隆。
实现了Serializable.可以被序列化。
二:分析下源码:
1.有两种构造方法,List
List
//ArrayList带容量大小的构造函数
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity]; //新建一个数组初始化elementData
}
//不带参数的构造函数
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;//使用空数组初始化
//elementData,EMPTY_ELEMENTDATA的值为10
}
2.每次集合添加对象时,会检查集合的大小是否满足。会调用ensureCapacity(size+1)方法。确保集合能容纳该元素。
//将e添加到ArrayList中
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
每次会添加1/2容量。源码即: int newCapacity = oldCapacity + (oldCapacity >> 1);
//给数组扩容,该方法是提供给外界调用的,是public的,真正扩容是在下面的private方法里
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != EMPTY_ELEMENTDATA)
// any size if real element table
? 0
// larger than default for empty table. It's already supposed to be
// at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
private void ensureCapacityInternal(int minCapacity) {
//如果是个空数组
if (elementData == EMPTY_ELEMENTDATA) {
//取minCapacity和10的较大者
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//如果数组已经有数据了
ensureExplicitCapacity(minCapacity);
}
3.ArrayList:添加一个元素到指定位置的时候,其后面的元素需要整体移动一位。
删除一个元素时候,后面的元素同样需要整体向前移动一位。 所以删除和添加相对比较耗资源。
//将element添加到ArrayList的指定位置
public void add(int index, E element) {
rangeCheckForAdd(index);//检查下标是否符合要求
ensureCapacityInternal(size + 1); // Increments modCount!!
//将index以及index之后的数据复制到index+1的位置往后,即从index开始向后挪了一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element; //然后在index处插入element
size++;
}
然后查询就不一样了。因为插入的时候,已经标记了下标,所以可以直接根据下标来获取。所以速度是很快的。
remove(Object obj)和remove(int one)相比较。后面一个速度快。因为前面一个首先需要遍历集合,找到该元素的下标,
然后再调用remove(int one).
4. contains(Object obj)这个方法也是需要遍历集合来查找是否有没有相等的元素。
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
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;
}
5.关于fail-fast机制。
即A线程在通过迭代器来遍历集合x时候,如果B线程修改了集合x的结构,就会报ConcurrentModificationException 异常.
private class Itr implements Iterator {
int cursor;
int lastRet = -1;
int expectedModCount = ArrayList.this.modCount; //expectedModCount 不会进行修改
public boolean hasNext() {
return (this.cursor != ArrayList.this.size);
}
public E next() {
checkForComodification();
/** 省略此处代码 */
}
public void remove() {
if (this.lastRet < 0)
throw new IllegalStateException();
checkForComodification();
/** 省略此处代码 */
}
final void checkForComodification() {
if (ArrayList.this.modCount == this.expectedModCount)
return;
//expectedModCount 不会进行修改,如果modCount 的改变了,就会抛异常。
throw new ConcurrentModificationException();
}
}
而每一次的修改ArrayList的结构,比如添加,删除等,都会调用ensureExplicitCapacity(int one);从而来改变modCount的值。
private void ensureExplicitCapacity(int paramInt) {
this.modCount += 1; //修改modCount
/** 省略此处代码 */
}
故这就验证了上面的结论。遍历的时候,另外的线程对该集合进行修改,就可能抛异常。
解决的方案:
方案一:在遍历过程中所有涉及到改变modCount值得地方全部加上synchronized或者直接使用Collections.synchronizedList, 这样就可以解决。但是不推荐,因为增删造成的同步锁可能会阻塞遍历操作。
方案二:使用CopyOnWriteArrayList来替换ArrayList。推荐使用该方案。
总体来说。随机访问是直接根据下标(和数组一样)来获取的。所以速度快。增加,删除需要整体移动。所以会比较耗资源。
所谓的效率比较,只要知道了原理,就好比较了