Vector源码分析
Vector简介
Vector
在JDK 1.0
被引入,基于数组实现,并且是一个动态数组,其容量可以自动增长。在很多方法的实现上,Vector
加入了同步语句,因此一般来说Vector
是线程安全的,可以在多线程环境中运用。
本文对Vector
源码的分析基于JDK 1.8.0_111
,并仅对常用的方法进行分析。
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
Vector
类继承自AbstractList
,AbstractList
是一个抽象类,实现了List
接口,AbstractList
提供了实现一个List
的基本骨架,包括add
,remove
,get
,set
,indexOf
等方法。Vector
实现了RandomAccess
接口,因此可以随机访问。实现了Cloneable
接口,允许克隆。实现了Serializable
接口,可以被序列化。
实例变量
Vector
内包含3
个实例变量。
protected Object[] elementData;
protected int elementCount;
protected int capacityIncrement;
elementData
是一个数组,用于存储Vector
容器的元素,该数组有一个对应的属性capacity
,capacity
描述了elementData
当前的长度。elementCount
描述了elementData
内有效元素的个数。capacityIncrement
刻画了当要存储的元素的个数大于elementData
的capacity
时,elementData
需要扩容的空间大小,即容器增长系数。
构造方法
Vector
定义了4
个构造方法。
-
Vector(int initialCapacity, int capacityIncrement);
-
Vector(int initialCapacity);
-
Vector();
-
Vector(Collection<? extends E> c);
当我们直接new
一个Vector
时,即调用Vector
的默认构造方法,实际上,Vector
的默认构造方法调用this(10)
,即调用Vector(int initialCapacity)
,并将initialCapacity
设置为10
,接着Vector(int initialCapacity)
再调用this(initialCapacity, 0)
,并将capacityIncrement
设置为0
。从以上我们可以得知,对于一个默认构造的Vector
对象,它的默认存储空间可以存储10
个元素,且容器增长系统等于0
。
私有方法
Vector
定义了3
个重要的私有方法ensureCapacityHelper(int minCapacity)
、void grow(int minCapacity)
和int hugeCapacity(int minCapacity)
,这3
个方法用于实现Vector
的自动扩容,Vector
中很多涉及到影响Vector
元素变化的操作都会直接或者间接的调用这3
个方法,源码如下。
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
我们可以关注Vector
的add(E e)
方法,在往Vector
内新增元素前,需要确保存储Vector
元素的数组elementData
有充足的空间,这一步通过ensureCapacityHelper
来实现,ensureCapacityHelper
接收elementCount + 1
作为参数,确保Vector
至少有一个空闲的空间存储新增的元素。当Vector
不具备多余的空间时(minCapacity - elementData.length > 0
),ensureCapacityHelper
就会调用grow
方法扩充elementData
数组。在扩充数组时,如果capacityIncrement
不大于0
,那么Vector
就会开辟出2
倍于原elementData
数组长度的空间,并通过Arrays.copyOf
将原elementData
中的元素拷贝到新地址空间。另外,Vector
容量的扩充也是有限制的,从hugeCapacity
方法可以看出,Vector
的容量最大扩充至Integer.MAX_VALUE
。
Vector
中定义了大量的用于操作容器的方法,其中绝大多数设计到改变容器大小的方法都是在底层做两件事,一是开辟新的存储空间(如果有必要),二是进行数组的拷贝。这些方法理解起来并不难,此处不再赘述。
线程安全
在很多面试环节中,经常被问到的问题是ArrayList
和Vector
的区别是什么?关于这个问题,最频繁的回答可能是Vector
是线程安全的,而ArrayList
是非线程安全的。这样回答的原因是在Vector
的方法中,几乎所有的操作容器的方法上都加了synchronized
,这意味着访问这些方法前都需要获得对象的锁。因此,这些方法不会被多个线程同时访问,从而实现线程安全。
Java并发编程实战一书中提到:如果只是将每个方法都作为同步方法(比如Vector
,简单的在方法前加上synchronized
),那么并不足以确保复合操作是原子的,以Vector
为例,观察如下代码片段。
if(!vector.contains(element)) {
vector.add(element);
}
这是一个经典的put-if-else
问题,虽然通过synchronized
已确保contains
方法和add
方法都是原子的,但是如果把多个操作合并为一个复合操作,仍旧需要额外的加锁机制。否则,多线程环境下,在contains
方法和add
方法的执行间隙期间完全有可能经历一个线程获取contains
上的锁,执行完contains
方法后释放锁,然后锁又被另一线程获取,并执行了add
方法,然后释放锁,锁又被原来执行contains
方法的线程获取,然后执行add
方法,此时,该线程可能已经基于一个错误的假设在执行add
方法了(Vector
内可能已经存在该线程即将要add
的元素了)。