ArrayList源码和相关问题分析
1、底层原理
ArrayList底层是用数组实现的存储。通过无参构造方法的方式ArrayList()初始化,则赋值底层数Object[] elementData为一个默认空数组Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}所以数组容量为0,只有真正对数据进行添加add时,才分配默认DEFAULT_CAPACITY = 10的初始容量。通过有参构造方法初始化,可以创建指定长度的数组。
创建时指定初始容量和不指定的区别:如果能预估数据量大小,可以在定义ArrayList时指定数组初始大小,这样可以避免不必要的数组扩容,从而减小开销。
2、删除带来的问题
2.1、for循环删除
(1)正向删除
public static void main(String[] args){ List<String> list = new ArrayList<String>(); list.add("111"); list.add("222"); list.add("222"); list.add("333"); list.add("444"); list.add("333"); //for循环正向循环删除 for (int i = 0;i < list.size();i++){ if (list.get(i).equals("333")){ list.remove(i); } } System.out.println(Arrays.toString(list.toArray())); }
运行结果:[111, 222, 333, 444, 333]
分析:发现相邻的222没有被删除,原因是i=1位置的元素删除后,后面的元素都会向前移一位,下次循环会从i=2开始,而数据迁移后i=1位置的222就不会再被删除。所以for循环正向删除会以后相邻重复元素。
(2)反向删除
public static void main(String[] args){ List<String> list = new ArrayList<String>(); list.add("111"); list.add("222"); list.add("222"); list.add("333"); list.add("444"); list.add("333"); //for循环反向循环删除 for (int i = list.size() - 1;i >= 0;i--){ if (list.get(i).equals("222")){ list.remove(i); } } System.out.println(Arrays.toString(list.toArray())); }
反向删除不会有问题。
2.2、Iterator循环删除
(1)在iterator中使用ArrayList的remove( )方法
public static void main(String[] args){ List<String> list = new ArrayList<String>(); list.add("111"); list.add("222"); list.add("222"); list.add("333"); list.add("444"); list.add("333"); //foreach循环删除 Iterator iterator = list.iterator(); while (iterator.hasNext()){ if (iterator.next().equals("222")){ list.remove(iterator.next()); } } System.out.println(Arrays.toString(list.toArray())); }
运行结果:会报Exception in thread "main" java.util.ConcurrentModificationException
分析:看下ArrayList源码——list.remove( )和list.iterator( )
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; } 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 }
private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; Itr() {} 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]; }
list.iterator( )返回的是内部类Itr,它实现了Iterator接口,报错的原因就在于调用iterator.next( )时有一个checkForComodification( )方法会做一个检查
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
list.remove( )在删除元素时,只做了modCount ++,所以检查时两个值会不相等。
(2)使用iterator的remove( )方法
public static void main(String[] args){ List<String> list = new ArrayList<String>(); list.add("111"); list.add("222"); list.add("222"); list.add("333"); list.add("444"); list.add("333"); Iterator iterator = list.iterator(); while (iterator.hasNext()){ if (iterator.next().equals("222")){ iterator.remove(); } } System.out.println(Arrays.toString(list.toArray())); }
这种方式不会报错,且能正确删除元素(推荐使用的方式)。分析下源码:——看iteraotr的remove( )
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(); } }
其实调用的就是ArrayList的remove(),不过有一步:expectedModCount = modCount,所以不会报错。
2.3、foreach中删除
foreach原理是因为这些集合类都实现了Iterable接口,该接口中定义了Iterator迭代器的产生方法,并且foreach就是通过Iterable接口在序列中进行移动。也就是说:在编译的时候编译器会自动将对for这个关键字的使用转化为对目标的迭代器的使用。