List家族主要的类结构如下:
其中List 接口继承 Iterable 为标记其迭代能力, 其子类都应该实现迭代器的功能.
我们平常主要使用的List实现为. ArrayList (array实现), LinkedList(链表实现), CopyOnWriteList (线程安全). Vector也是线程安全, 但由于其线程安全方式过于简单, 直接使用悲观锁synchronize来实现线程安全, 也是比较少会用到. 接下来主要来了解一下各个实现类. 同时会涉及到几个没有在类图表现到的 内部实现类.
ArrayList 和 Vector
ArrayList作为效率比较高的可变长集合, 使用率非常高.
一,构造器:
/**
* 构造一个具有指定初始容量的空列表。
*/
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
/**
* 构造一个初始容量为 DEFAULT_CAPACITY (10) 的空列表.
* 在初始时没有长度,在第一次 add时, ensureCapacity时使用 DEFAULT_CAPACITY 来扩容长度
*/
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}
/**
* 构造一个包含指定 collection 的元素的列表
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
二,主要属性:
/**
* 没有指定初始长度时的默认长度
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 没有指定初始长度时,elementData指向的数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* ArrayList的容器.即ArrayList底层实现为数组.
*/
private transient Object[] elementData;
/**
* elementData已存放长度.
*/
private int size;
其中还有一个从AbstractList继承而来的属性要留意.
/**
* 外部对list结构的修改次数[如add,remove等操作]. 这个字段主要用来实现快速失败
* 即,在进行迭代时,会检查,这个字段是否和一开始进行迭代不同. 从而确定list是否被其它线程修改.
* 如果修改, 则抛出异常, 从而快速失败. 而不是迭代完. 产生不确定的行为.
*/
protected transient int modCount = 0;
三,方法
add(E e)方法简析.
/**
* 将元素添加到尾部
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 在这个方法里确保elementData长度足够, 并且进行modCount++, 注意, 此处用的是size
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) { //minCapacity. add成功需要的最小elementData 长度.
if (elementData == EMPTY_ELEMENTDATA) {//如果没有指定初始长度,并且第一次add.最小长度取10
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //结构修改次数+1
// overflow-conscious code
if (minCapacity - elementData.length > 0) //add成功需要的最小数组长度 minCapacity与原 elementData 最大容量比较. 如果minCapacity大于最大容量, 则需要进行扩容.
grow(minCapacity);
}
/**
* 具体扩容算法.
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
indexOf方法解析
/**
* 通过遍历的方式, 确定o的位置.
*/
public int indexOf(Object o) {
if (o == null) { //如果o是null. 则无法通过 equals比较.
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
remove方法解析:
public E remove(int index) { //注, remove时, 不会减小,elementData已扩容的容量.需要缩小, 主动调用 trimToSize方法即可.
rangeCheck(index); //检查范围是否合适
modCount++; //修改次数+1
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0) //大于0说明移除不是最后一个元素.如果是最后一个,不用移动. 具体remove算法. 通过数组移动来实现
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // 把空出来的置为null, 可让gc及时回收
return oldValue;
}
其它的常用方法如,contains, indexOf等, 比较简单, 通过遍历来获取是否包含和位置, 此处不再说明. addAll以及add和remove的重载方法, 基本和上面的add和romove大同小异. 如 add(int index,E e)
public void add(int index, E element) {
rangeCheckForAdd(index); //检查index是否合适
ensureCapacityInternal(size + 1); // 类似 add,确保elementData容量.
System.arraycopy(elementData, index, elementData, index + 1,
size - index); //位置移动
elementData[index] = element; //添加.
size++;
}
说明: transient 关键字作用, 表明, 该字段序列化时不会被序列化. 其实知道其意思(瞬时的)就基本理解了.
到这里我们基本理解了ArrayList这个类了。 但是, 我们可以看到,elementData是加了 transient的。 但是实际上, ArrayList是可以正常序列化和反序列化的。测试便可知道, 是怎么做到的呢 ? 其实, transient只是告诉默认的序列化器, 不要将字段序列化。 但是我们在io那里学过, 有一种流是专门来写对象和读对象。并且, 序列化与反序列化时, 调用的是Object的 readObject和writeObject。 即,ArrayList 覆盖了 readObject和writeObject实现对elementData的读写。
方法如下:
/**
* Save the state of the <tt>ArrayList</tt> instance to a stream (that
* is, serialize it).
*
* @serialData The length of the array backing the <tt>ArrayList</tt>
* instance is emitted (int), followed by all of its elements
* (each an <tt>Object</tt>) in the proper order.
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
int expectedModCount = modCount;
s.defaultWriteObject(); //写入默认的字段(即没有transient修饰的字段)
//写入长度字段,作为兼容.这个后面解释.可理解为这个是额外写入的.
s.writeInt(size);
// 将所有对象写入对象流
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) { //检查是否在序列化时其它线程修改了list
throw new ConcurrentModificationException();
}
}
/**
* Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
* deserialize it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// 读取默认写入的字段(包括size)
s.defaultReadObject();
// 跳过 writeObject时写入的兼容size,后面解释
s.readInt();
if (size > 0) {
ensureCapacityInternal(size);
Object[] a = elementData;
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
四,简单总结如下:
- ArrayList基于数组实现. 自动扩容. 因此使用起来比数组方便很多.
- ArrayList也有数组的优缺点. 如, 基于索引查找速度很快.基于内容查找则需要遍历.遍历快,删除和添加较慢.
- 带索引的方法不需要遍历数组,不带索引需要遍历数组. 如 remove(int index) 和 remove(E e).
- contains(E e) 和 indexOf(E e) 都需要进行一次遍历.
- remove特定位置数据和add时, 需要移动较多数据, 因此, 对于某些需要大量经常remove和add的操作不是很合适.
p.s 另外, 上面说到, readObject 和 writeObject 都处理了 size 这个非 transient字段, 这是为什么 ?
原因: 兼容旧版本的 readObject 和 writeObject. (旧版本把 elementData的length也写入了).
/**
* Save the state of the <tt>ArrayList</tt> instance to a stream (that
* is, serialize it).
*
* @serialData The length of the array backing the <tt>ArrayList</tt>
* instance is emitted (int), followed by all of its elements
* (each an <tt>Object</tt>) in the proper order.
*/
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();
}
}
/**
* Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
* deserialize it).
*/
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();
}