java集合框架List之ArrayList
list作为我们日常工作中常用的一种集合容器,有必要对这进行一个详细的了解。一个有序的Collection(也称序列),元素可以重复。确切的讲,列表通常允许满足 e1.equals(e2) 的元素对 e1 和 e2,并且如果列表本身允许 null 元素的话,通常它们允许多个 null 元素。实现List的有:ArrayList、LinkedList、Vector等,下面我们先对其中之一的ArrayList进行介绍。
ArrayList的继承结构
ArrayList是基于动态数组的数据结构实现的,既然是基于数组的结构,那么它对于随机访问访问的set和get的速度是很快的,时间复杂度为O(1),同样的,对于删除和增加操作就会慢很多了,所以,我们需要根据自己的应用场景来灵活使用。
ArrayList操作示例
List<Integer> list = new ArrayList<>();
// 初始化添加
list.add(1);
list.add(2);
list.add(3);
// 使用Iterator遍历
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next().toString());
}
// 使用循环遍历
for (Integer i : list){
System.out.println(i.toString());
}
// 移除
list.remove(0);
Iterator<Integer> iterator2 = list.iterator();
while (iterator2.hasNext()){
System.out.println(iterator2.next().toString());
}
ArrayList是非线程安全的,也就是说它对于并发操作很容易出现脏读或脏写,我们看一下ArrayList读写操作的内部实现
// 增加一个元素
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
// 删除一个元素
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;
return oldValue;
}
可以看到,在无任何锁保护的前提下,对一个位置进行插入时,两个线程对同一个ArrayList拿到的size是一样大的,那么最先写入那个位置的值会被后面的写入所覆盖;删除操作也是同样的,A线程已经将数据删除,B数据再去删除位置,很容易抛出ArrayIndexOutOfBoundsException,所以对于无法保证线程安全的场景下,我们不应该选用ArrayList作为我们的容器选择,那么应该选用哪一个呢?了解了其它的集合容器后我们就知道啦。
有趣的modCount
ArrayList的modCount是从类 java.util.AbstractList 继承的字段
protected transient int modCount
是指从结构上修改此列表的次数。从结构上修改是指更改列表的大小,或者打乱列表,从而使正在进行的迭代产生错误的结果。
此字段由 iterator 和 listIterator 方法返回的迭代器和列表迭代器实现使用。如果意外更改了此字段中的值,则迭代器(或列表迭代器)将抛出 ConcurrentModificationException 来响应 next、remove、previous、set 或 add 操作。在迭代期间面临并发修改时,它提供了快速失败行为,而不是非确定性行为。
子类是否使用此字段是可选的。如果子类希望提供快速失败迭代器(和列表迭代器),则它只需在其 add(int, E) 和 remove(int) 方法(以及它所重写的、导致列表结构上修改的任何其他方法)中增加此字段。对 add(int, E) 或 remove(int) 的单个调用向此字段添加的数量不得超过 1,否则迭代器(和列表迭代器)将抛出虚假的 ConcurrentModificationExceptions。如果某个实现不希望提供快速失败迭代器,则可以忽略此字段。
示例如下
List<Integer> list = new ArrayList<>();
// 初始化添加
list.add(1);
list.add(2);
list.add(3);
// 使用Iterator遍历
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next().toString());
list.add(4);//在迭代过程中对list进行操作
}
在迭代过程中对list进行操作会导致modCount与迭代器内的modCount不一致而快速失败,抛出ConcurrentModificationExceptions异常
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
1
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at com.nfsq.tzs.base.Test.main(Test.java:38)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
对于这种情况我们应该使用迭代器自己的add或者remove等来保证modCount的一致性。