如何扩容?
add() 方法
在调用 add 方法时,会调用 ensureCapacityInternal(size + 1);
方法
1 2 3 4 5 6 7
| private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); }
|
上述代码会首先判断内部数组 elementData 是否为空,如果为空的话,则至少分配 DEFAULT_CAPACITY (初始值为10) 的大小,接下来会调用 ensureExplicitCapacity(minCapacity);
方法:
1 2 3 4 5 6 7
| private void ensureExplicitCapacity(int minCapacity) { modCount++; if (minCapacity - elementData.length > 0) grow(minCapacity); }
|
其中 modCount 表示内部的修改次数。
如果需要的长度大于了当前数组的长度,则调用 grow()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| private void grow(int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); }
|
1 2 3 4 5 6 7
| private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
|
remove() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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; }
|
它也增加了modCount,然后计算要移动的元素个数,从index往后的元素都往前移动一位,实际调用System.arraycopy方法移动元素。elementData[–size]=null;这行代码将size减1,同时将最后一个位置设为null,便于JVM进行垃圾回收。
迭代器
迭代器的标准写法为:
1 2 3 4
| Iterator<Integer> it = intList.iterator(); while(it.hasNext()) { System.out.println(it.next()); }
|
其语法糖为 foreach
:
1 2 3
| for(Integer a : intList) { System.out.println(a); }
|
迭代器接口
ArrayList实现了Iterable接口,Iterable表示可迭代,Java 7中的定义为
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public interface Iterable<T> { Iterator<T> iterator(); default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } default Spliterator<T> spliterator() { return Spliterators.spliteratorUnknownSize(iterator(), 0); } }
|
NOTE: default 方法是 java 8 中新增加的一种为接口添加默认实现方法的扩展方式。
它返回一个实现了Iterator接口的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public interface Iterator<E> { boolean hasNext(); E next(); default void remove() { throw new UnsupportedOperationException("remove"); } default void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); while (hasNext()) action.accept(next()); } }
|
hasNext()判断是否还有元素未访问,next()返回下一个元素,remove()删除最后返回的元素. 只要对象实现了Iterable接口,就可以使用foreach语法,编译器会转换为调用Iterable和Iterator接口的方法。
- Iterable表示对象可以被迭代,它有一个方法iterator(),返回Iterator对象,实际通过Iterator接口的方法进行遍历;
- 如果对象实现了Iterable,就可以使用foreach语法;
- 类可以不实现Iterable,也可以创建Iterator对象。
如何正确地在ArrayList中删除某一个元素
下面的这段代码会抛出 ConcurrentModificationException
,原因是因为迭代器内部会维护一些索引位置相关的数据,要求在迭代过程中,容器不能发生结构性变化,否则这些索引位置就失效了。所谓结构性变化就是添加、插入和删除元素,只是修改元素内容不算结构性变化。
1 2 3 4 5 6 7
| public void remove(ArrayList<Integer> list) { for(Integer a : list) { if(a<=100) { list.remove(a); } } }
|
正确地方法是使用 Iterator 的 remove 方法
1 2 3 4 5 6 7 8
| public static void remove(ArrayList<Integer> list) { Iterator<Integer> it = list.iterator(); while(it.hasNext()) { if(it.next() <= 100) { it.remove(); } } }
|
迭代器 remove 方法的实现原理
ArrayList 中 iterator 方法的实现为:
1 2 3
| public Iterator<E> iterator() { return new Itr(); }
|
新建了一个Itr对象,Itr是一个成员内部类,实现了Iterator接口,声明为:
1 2 3 4 5
| private class Itr implements Iterator<E> { int cursor; int lastRet = -1; int expectedModCount = modCount; }
|
其中比较重要的是 expectedModCount
表示期望的修改次数,其值被初始化为 ArrayList 中的当前修改次数 modCount
。由于发生结构性变化的时候modCount都会增加,而每次迭代器操作的时候都会检查expectedModCount是否与modCount相同,这样就能检测出结构性变化。
为什么通过迭代器去删除元素时,必须调用 next() 方法?
1 2 3 4 5 6
| public static void removeAll(ArrayList<Integer> list){ Iterator<Integer> it = list.iterator(); while(it.hasNext()){ it.remove(); } }
|
上面这段代码,实际运行时会抛出 java.lang.IllegalStateException
异常,正确的写法是:
1 2 3 4 5 6 7 8
| public static void removeAll(ArrayList<Integer> list) { Iterator<Integer> it = list.iterator(); while(it.hasNext()) { it.next() it.remove(); } }
|
要想知道原因,我们就需要看看 next()
的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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(); }
|
next前面部分主要就是在检查是否发生了结构性变化,如果没有变化,就更新cursor和lastRet的值,以保持其语义,然后返回对应的元素。
我们再来看看 remove()
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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()
就直接调用 remove()
方法,会导致 cursor 和 lastRet 的指向位置不正确。
一些 ArrayList 性能相关的总结
public ArrayList(int initialCapacity)
如果能够在业务场景能够确定 ArrayList 容量大小的情况下,可以使用该构造函数指定初始化的容量大小,这样能够尽量的减少 ArrayList 由于自动扩容造成的额外的内存分配和数组 Copy 。
public void ensureCapacity(int minCapacity)
1 2 3 4 5 6 7 8 9 10 11 12
| public void ensureCapacity(int minCapacity) { int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // any size if not default element table ? 0 // larger than default for default empty table. It's already // supposed to be at default size. : DEFAULT_CAPACITY; if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } }
|
它可以确保数组的大小至少为minCapacity,如果不够,会进行扩容。如果已经预知ArrayList需要比较大的容量,调用这个方法可以减少ArrayList内部分配和扩展的次数。
public void trimToSize()
1 2 3 4 5 6 7 8 9 10 11 12 13
| /** * Trims the capacity of this <tt>ArrayList</tt> instance to be the * list's current size. An application can use this operation to minimize * the storage of an <tt>ArrayList</tt> instance. */ public void trimToSize() { modCount++; if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } }
|
使用该方法,能将 ArrayList 的内部数组容量减少至实际的存储空间大小,从而达到节约内存的目的。
ArrayList 的复杂度分析以及应用场景
- 可以随机访问,按照索引位置进行访问效率很高,用算法描述中的术语,效率是O(1),简单说就是可以一步到位。
- 除非数组已排序,否则按照内容查找元素效率比较低,具体是O(N),N为数组内容长度,也就是说,性能与数组长度成正比。
- 添加元素的效率还可以,重新分配和复制数组的开销被平摊了,具体来说,添加N个元素的效率为O(N)。
- 插入和删除元素的效率比较低,因为需要移动元素,具体为O(N)。