这三者都是实现集合框架中的List,也就是所谓的有序集合,因此功能比较近似,比如都是按照位置进行定位,添加,删除的操作,都提供迭代器遍历集合的内容。但是因为具体的设计区别,它们三个在行为,性能,线程安全方面又有着截然不同的表现。
Vector是Java早期提供的线程安全的动态数组,如果不需要线程安全,不建议选择它。毕竟同步是有额外开销的。Vector内部是根据对象数组来保存数据的,并且根据需要自动增加数组的容量,当数组已满,会创建新的数组,并拷贝原有的数组,因为这些操作是在内部实现的,所以对于使用者来说是没什么感觉的。
ArrayList是应用更加广泛的动态数组实现的,它本身不是线程安全的,所以性能要好很多,与Vector类似,ArrayList也是可以自动调整容量的,不过两者调整的逻辑有些区别,Vector默认是在扩容时会提高1倍,ArrayList则是增加50%。
LinkedList是由Java提供的双向链表实现的,所以它不需要像上面需要扩容,同时它也不是线程安全的。
Vector和ArrayList作为动态数组,其内部元素是以数组形式存储的,所以非常适合随机访问的场合,除了尾部插入和删除元素,往往性能会比较差,比如我们要在中间插入一个元素,首先需要将这个元素的后面元素都向后移动1位,而LinkedList是进行节点插入,删除高效的多,但是随机访问性能要比动态数组慢的多。所以我们总结一下,当我们要操作的集合插入删除的机会大于查询的几率,选择LinkedList集合,反之,查询的几率大于插入,删除的几率,可以选择Vector,ArrayList的,同时又因为在不考虑线程安全的角度,Vector的效率不如ArrayList,所以我们使用的集合大多都是ArrayList的。
虽然ArrayList和LinkedList不是线程安全的,但并不代表这些集合完全不能支持并发编程的场景。在Collections工具类中,提供了一系列synchronized的方法。比如
static <T> List<T> synchronizedList(List<T> list),我们完全可以利用类似的方法来实现基本的线程安全集合:
List list = Collections.synchronizedList(new ArrayList()); 它的实现基本就是将每个基本方法,比如get,set,add方法,都通过synchronized添加基本的同步支持,非常简单粗暴,但也非常实用,注意这些方法创建的线程安全集合,都符合迭代时fail-fast行为,当发生意外的并发修改时,尽早抛出ConcurrentModificationException 异常,以避免不可预计的行为。
接下来讲一下ArrayList的扩容原理:
ArrayList内部是用一个对象数组来存储数据的。提供插入数据的add()方法,在插入的过程中,判断当前的容量是否满足要求,不满足的话,就会扩容原来的1.5倍,把当前的数据拷贝到新创建的数组。
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
public void add(int index, E element) {
rangeCheckForAdd(index);
modCount++;
final int s;
Object[] elementData;
if ((s = size) == (elementData = this.elementData).length)
elementData = grow();
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
elementData[index] = element;
size = s + 1;
}
可以看出重构的add()方法中,在数组容量不足的情况,都会调用grow()方法
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
private Object[] grow() {
return grow(size + 1);
}
在grow(int minCapacity)的方法中,我们可以看到调用Arrays的静态拷贝函数,先创建一个newCapacity(minCapacity)大小的对象数组,通过拷贝函数System.arraycopy把elementData 数组内容拷贝到新数组中返回。
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity <= 0) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE)
? Integer.MAX_VALUE
: MAX_ARRAY_SIZE;
}
newCapacity(int minCapacity)这个函数才是把当前的数组容量扩大的原来的1.5呗,可以看到数组的容量如果大于MAX_ARRAY_SIZE的话,调用hugeCapacity(minCapacity)这个函数,而hugeCapacity这个函数返回的最大的长度是Integer.MAX_VALUE,那么是否也就是说ArrayList内部能够存储的最大字节是Integer.MAX_VALUE吗?我觉得最好把最大字节设置为MAX_ARRAY_SIZE,毕竟还要有8个字节来存储一个数组头的相关信息,一旦超过了MAX_ARRAY_SIZE,可能会出现某些不可预料的事情。
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
如果你有兴趣的朋友了解Hashtable,HashMap,TreeMap之间的区别,可以看我的下一篇文章。Hashtable,HashMap,TreeMap之间的区别