ArrayList就是动态数组,用MSDN中的说法,就是Array的复杂版本,它提供了动态的增加和减少元素,实现了ICollection和IList接口,灵活的设置数组的大小等好处。
ArrayList的属性说明:
- private transient Object[] elementData;
The array buffer into which the elements of the ArrayList are stored.
The capacity of the ArrayList is the length of this array buffer.
- private int size;
The size of the ArrayList (the number of elements it contains).
- protected transient int modCount = 0;
The number of times this list has been <i>structurally modified</i>
已从结构上修改 此列表的次数。从结构上修改是指更改列表的大小,或者打乱列表,从而使正在进行的迭代产生错误的结果。
此属性继承自AbstractList。
疑问一:ArrayList是一个动态数组,如何实现数组的动态呢?
- 在创建(new)的时候,默认会将 elementData属性设置为容量为10的数组,或者按照指定大小设置elementData的初始容量。具体代码如下:
/** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this(10); }
/** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list * @exception IllegalArgumentException if the specified initial capacity * is negative */ public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; }
- 在向ArrayList增加元素时,比如add方法时,首先会调用方法
public void ensureCapacity(int minCapacity)
如有必要,增加此 ArrayList 实例的容量,以确保它至少能够容纳最小容量参数所指定的元素数。
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { ensureCapacity(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
/** * Increases the capacity of this <tt>ArrayList</tt> instance, if * necessary, to ensure that it can hold at least the number of elements * specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ public void ensureCapacity(int minCapacity) { modCount++; int oldCapacity = elementData.length; //判断要求的容量大于elementData数组现在的容量 if (minCapacity > oldCapacity) { Object oldData[] = elementData; //增加容量为现有容量的1/2+1,为什么加1,考虑原容量为0或者1的情况? int newCapacity = (oldCapacity * 3)/2 + 1; //如果自动扩容后依然小于要求容量,则按要求容量作为新数组容量 if (newCapacity < minCapacity) newCapacity = minCapacity; // minCapacity is usually close to size, so this is a win: //复制原数据,生成新的扩容数组 elementData = Arrays.copyOf(elementData, newCapacity); } }
疑问二:继承属性modCount的作用:
表示已从结构上修改 此列表的次数。从结构上修改是指更改列表的大小,或者打乱列表,从而使正在进行的迭代产生错误的结果。
此属性的默认值是0,当结构上修改如向列表中增加、减少元素时,执行modCount++,可参考以上的add方法。在其迭代器Iterator调用时,或者执行方法writeObject时,会检查modCount的值是否发生变更,如果是,则抛出异常ConcurrentModificationExceptions。代码如下:
ArrayList的获取迭代器的方法继承自父类AbstractList:
public Iterator<E> iterator() { return new Itr(); }
需要时创建新的迭代器,即创建实现了Iterator接口的AbstractList的内部类Itr 。其中属性在创建时设置。
/** The modCount value that the iterator believes that the backing * List should have. If this expectation is violated, the iterator * has detected concurrent modification. **/ int expectedModCount = modCount;
在调用Itr其中的next和remove的时候,会检查ArrayList的结构是否发生变更,方法checkForComodification()
public E next() { checkForComodification(); try { E next = get(cursor); lastRet = cursor++; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } }
如果方法变更,则抛出异常。
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
疑问三:ArrayList的数组属性elementData前面的修饰符transient涉及序列化。
序列化有2种方式:
A、只是实现了Serializable接口。
序列化时,调用java.io.ObjectOutputStream的defaultWriteObject方法,将对象序列化。
注意:此时transient修饰的字段,不会被序列化。
B、实现了Serializable接口,同时提供了writeObject方法。
序列化时,会调用该类的writeObject方法。而不是java.io.ObjectOutputStream的defaultWriteObject方法。
注意:此时transient修饰的字段,是否会被序列化,取决于writeObject。
ArrayList的源码:
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ // Write out element count, and any hidden stuff int expectedModCount = modCount; s.defaultWriteObject(); // Write out array length s.writeInt(elementData.length); // Write out all elements in the proper order. for (int i=0; i<size; i++) s.writeObject(elementData[i]); if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } }
ArrayList实现了java.io.Serializable,复写了以下2个方法:
private void writeObject(java.io.ObjectOutputStream s)
private void readObject(java.io.ObjectInputStream s)
从源码中可以看出,先调用java.io.ObjectOutputStream的defaultWriteObject方法,进行默认的序列化操作,此时transient修饰的字段,没有被序列化。
接着:for循环,将数组中的元素写出,序列化。而数组中的元素正是transient。
疑问四:查找元素和添加删除元素的效率?
在动态数组中,如果我们要在某一个位置添加或者删除一个元素,剩下的每个元素都要相应地往前或往后移动。如果该动态数组中的元素很多,那么,每当我们添加或删除一个元素后,需要移动的元素就非常多,因此,效率就比较低。除了在动态数组末尾增加元素,这样不需要移动元素。
/** * Inserts the specified element at the specified position in this * list. Shifts the element currently at that position (if any) and * any subsequent elements to the right (adds one to their indices). * * @param index index at which the specified element is to be inserted * @param element element to be inserted * @throws IndexOutOfBoundsException {@inheritDoc} */ public void add(int index, E element) { if (index > size || index < 0) throw new IndexOutOfBoundsException( "Index: "+index+", Size: "+size); ensureCapacity(size+1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
/** * Removes the element at the specified position in this list. * Shifts any subsequent elements to the left (subtracts one from their * indices). * * @param index the index of the element to be removed * @return the element that was removed from the list * @throws IndexOutOfBoundsException {@inheritDoc} */ public E remove(int index) { RangeCheck(index); modCount++; E oldValue = (E) elementData[index]; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // Let gc do its work return oldValue; }
System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。