类继承关系图
ArrayList:先来看看最常用的ArrayList,它继承了AbstractList,实现了List, RandomAccess, Cloneable, Serializable四个接口。
常常听见ArrayList是用数组实现的,的确ArrayList中存储数据的结构就是一个对象数组,当new一个新的ArrayList,其实是new了一个长度为10(默认值)的对象数组。
其上的操作就是对数组的操作,只是更为简单而已。
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
this.elementData = new Object[initialCapacity];
}
自动变长机制:
印象很深的是ArrayList数组好用多了,之所以有这样的感觉,全都是因为ArrayList能够自动变长,而数组则是定长的,自动变长在编写程序时是极为方便的。
那么ArrayList是怎样实现自动变长的呢?OK,让我们先来分析add()方法
public boolean add(Object o) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = o;
return true;
}
可以看出,ensureCapacity()方法主要用来保证新增元素后不会越界,下面来看ensureCapacity
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;
elementData = new Object[newCapacity];
System.arraycopy(oldData, 0, elementData, 0, size);
}
}
此时就能看出自动变长是如何实现的了。当数组容量不够时先new一个新的数组,长度是原来的3/2,然后用System.arraycopy方法将数据从老数组copy到新数组,
由此可知,在改变数组长度时是需要开销的,就是说,变长是需要代价的。
迭代器:
使用List的迭代器时一定有所体会,当运行类似如下程序时,会抛出一个ConcurrentModificationException
Iterator iterator = list.iterator();
while(iterator.hasNext())
{
set.remove(iterator.next());
}
关于这点,JDK又是怎样实现的呢?思想就是将iterator看成一个“临界区”,只是不是多线程而是单个线程内部的“临界”。这里借助于一个modCount,定义如下:
protected transient int modCount = 0;
在迭代过程中不允许修改原始list的方法中(如add,remove)每次对modCount自增1,比如add方法中通过ensureCapacity方法让modCount++,而在调用迭代器时(实际
上是初始化迭代器时)将modCount赋值给expectedModCount
int expectedModCount = modCount;
每次调用迭代器的next()方法就会进行checkForComodification()检查是否expectedModCount已经不等于 modCount,如果不等,说明在迭代过程中已经通过remove()或add()等方法对原集合进行过修改,这是不允许的,所以 checkForComodification()抛出ConcurrentModificationException异常;如果相等,说明迭代过程中集合没有被修改过。
为什么需要保证迭代中集合不能被修改呢?因为这样就不能保证集合能够被完全遍历到,因此,java的开发人员使用了这个比较巧妙的方法来解决这个问题。
public Object next() {
try {
Object next = get(cursor);
checkForComodification();
lastRet = cursor++;
return next;
} catch(IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
序列化:
ArrayList中最奇怪的就要数
private transient Object elementData[];
中transient标志了,序列化最主要的目的就是将这个对象数组序列化,可偏偏它却加上了transient关键字阻止它的序列化,真是令人费解。
查了些资料才知道,elementData中存放的都是对象的引用而不是真实对象,这就是说即使序列化也不能达到我们预想的效果,当你反序列化时这些引用 已经不指向原来的对象了,因此多了两个方法writeObject()和readObject()进行手工序列化或反序列化。
private synchronized void writeObject (java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
s.defaultWriteObject();
// Write out array length
s.writeInt(elementData.length);
// Write out all elements in the proper order.
for (int i=0; i
s.writeObject(elementData[i]);
}
private synchronized 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();
elementData = new Object[arrayLength];
// Read in all elements in the proper order.
for (int i=0; i
elementData[i] = s.readObject();
}
LinkedList:
LinkedList的实现类似数据结构中的双向链表,每个结点是这样一个结构:
private static class Entry {
Object element;
Entry next;
Entry previous;
}
构造一个带头节点的指针,请注意,这个链表是循环双向的,因此从头指针可以很容易找到尾节点。
private transient Entry header = new Entry(null, null, null);
LinkedList实现了一些特有的方法,比如 getFirst(),getLast(),addFirst(),addLast(),removeFirst(),removeLast(),看到这 个立马就想到队列,yes,完全正确,当你需要实现自己的queue时,LinkedList可是比较好的选择。
比较下ArrayList、LinkedList、Vector:
正如数组和链表的优劣一样,承袭了二者的ArrayList和LinkedList也是各有优劣。
ArrayList适合查找,快速定位,一般需要使用变长的数组就非他莫数了。
LinkedList适合频繁插入、删除,尤其是在表头尾的插入删除操作。
Vector中,非常类似ArrayList,但是其中加入了同步锁,不同步的时候使用它是会降低效率的。