java.util.ArrayList.java:
疑问:
-
transient
在这里类中发现了以前没有见过的transient关键字。这个关键字的主要标示这个属性不需要自动序列化。因为ArrayList实现了java.io.Serializable接口,所以ArrayList对象可以序列化到持久存储介质中。在这个类中,serialVersionUID和size都将自动序列化到介质中,但elementData数组对象却定义为transient了。也就是说 ArrayList中的所有这些元素都不会自动系列化到介质中。为什么要这样实现?因为elementData数组中存储的“元素”其实仅是对这些元素的 一个引用,并不是真正的对象,序列化一个对象的引用是毫无意义的,因为序列化是为了反序列化,当你反序列化时,这些对象的引用已经不可能指向原来的对象 了。所以在这儿需要手工的对ArrayList的元素进行序列化操作。这就是writeObject()的作用。
-
modCount
在这个类中,有很多地方出现了modCount这个变量。这个变量是在该类的超类AbstractArrayList中定义的。它是用来记录了ArrayList结构性变化的次数的。那么这个结构性变化的次数有什么用呢?还是在AbstractArrayList中,可以利用iterator()方法生成一个实现了Iterator接口的Itr对象。因为这个Itr类是AbstractArrayList的一个私有内部类,所以这个类可以自由的访问AbstractArrayList的属性和方法。Itr对象在初始化的时候,初始化了其内部的一个属性:int expectedModCount = modCount;当我们遍历这个对象内部的属性的时候,需要比较这 expectedModCount和modCount的值是否相等,以确保我们遍历的是同一个对象。这个可能在多线程的编程中非常有用。假如我们在一个线程中遍历这个集合,而在遍历的过程中另外的一个线程修改了集合的结构,这样就会抛出ConcurrentModificationException异常。
构造函数:
ArrayList一共三个构造函数:
public ArrayList(int initialCapacity) | 根据给定数值initialCapacity初始化initialCapacity大小的ArrayList |
public ArrayList() | 默认初始化长度为10的ArrayList |
public ArrayList(Collection<? extends E> c) | 由已存在的Collection构造并初始化一个新的ArrayList |
主要方法:
-
public void trimToSize()
{
modCount++;
int oldCapacity = elementData.length;
if (size < oldCapacity) {
elementData = Arrays.copyOf(elementData, size);
}
}
释放多余的空间。如果存入的元素大小小于分配的空间大小,则将释放掉空余的空间。该方法会引起ArrayList实例结构改变,因此modCount需要加1。
-
public void ensureCapacity(int minCapacity)
{
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity)
{
Object oldData[] = elementData;
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);
}
}
增加ArrayList实例的容量。参数minCapacity是实例扩容时所必须保证的最小值。从程序上来看,每次的扩容实际上增加的是原先实例容量的1.5倍再加1。该方法会引起ArrayList实例结构的改变,所以modCount自加1。
public int indexOf(Object o) | public int lastIndexOf(Object o) |
返回元素在结构中的位置。从程序中可以看出来indexOf返回的是该元素第一次出现的位置,而lastIndexOf则返回该元素在实例中最后一次出现的位置。如果给定的求解元素为null的话,也会得到第一个或者最后一个为null的位置。
-
public Object clone()
{
try
{
ArrayList<E> v = (ArrayList<E>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e)
{
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}
方法名顾名思义,克隆现有的ArrayList实例。但是这里需要注意的是对实力元素的copy是一种浅copy,也就是说新克隆出的ArrayList实例中的元素和原实例中的元素是同一个元素,是共享的,改变一个就会改变另外一个。因此使用该方法的时候要加以注意。]
-
增加元素
public boolean add(E e)
{
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}增加一个元素,在增加前先对容量进行修改,在这里会修改实例的结构,因此modCount会加1,然后将增加的元素加入到实例的末尾。
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++;
}
在指定位置增加元素。和上面的方法一样,首先对现有容量进行修改,同样也会修改实例的结构,modCount会加1。然后将指定位置之后的所有元素向后顺移一位,在将指定元素插入指定位置。这里注意,如果指定的位置超过了实例的上界和下界将会抛出IndexOutOfBoundsException异常。
public boolean addAll(Collection<? extends E> c)
{
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacity(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
将一个实现了Collection接口的类实例中的元素加入到现有的实例中。该操作会影响实例的结构,所以modCount自加1。调用这个方法需要注意一个问题,该操作执行的过程中不会注意的Collection是否已经被修改了。
public boolean addAll(int index, Collection<? extends E> c)
{
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: " + index + ", Size: " + size);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacity(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
这个方法和上一个方法差不多,只不过是在指定的位置插入一个实现了Collection接口的类的实例。插入之前,将该指定位置的元素挨个向后移动一位。同样,该操作会引起实例结构的改变。在执行操作之前,会判断指定的位置是否合法。
-
删除元素
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;
}
移除指定位置上的元素并且返回移除的元素。这个方法会先调用一个叫做RangeCheck的方法,这个方法用来检查给定的位置是否合法,如果非法,会抛出IndexOutOfBoundsException。该方法会改变实例的结构。移除之后,该位置之后的元素会向前顺移一位。源码中有一个很有意思的注释:Let gc do its work !这里可能会产生内存泄露的问题。
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;
}
这个方法是从实例中第0个元素开始查找指定的元素,如果查找到则移除指定的元素成功,否则失败。注意指定元素为null也可以被移除。在移除元素的时候,该方法将找到的指定元素的位置传递给fastRemove方法。
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; // Let gc do its work
}
该方法和public E remove(int index)方法的实现方式是一样的,只不过该方法不会返回被删除的元素。
protected void removeRange(int fromIndex, int toIndex)
{
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// Let gc do its work
int newSize = size - (toIndex-fromIndex);
while (size != newSize)
elementData[--size] = null;
}
移除指定位置之间的所有元素(前闭后开)。该方法会引起结构变动。
public void clear()
{
modCount++;
// Let gc do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
清除实例中的所有元素。该操作会引起实例结构的变化。
-
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(); } } | private void readObject(java.io. ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // Read in size, and any hidden stuff s.defaultReadObject(); // Read in array length and allocate //array int arrayLength = s.readInt(); Object[] a = elementData = new Object[arrayLength]; // Read in all elements in the proper //order. for (int i=0; i<size; i++) a[i] = s.readObject(); }
|
因为ArrayList实现了java.io.Serializable接口,因此该类可以被序列化。这两个方法就是序列化的写和读方法。在写方法中需要注意一点,在真正执行写之前,方法保存了modCount的copy,在写完之后会用该copy于modCount进行比较。如果不一致则抛出 ConcurrentModificationException异常。这是非常有意思的一点。该过程保证了待写元素在写的过程中没有被更改。