目录
1.Vector类的成员变量的含义
2.使用不同构造方法创建Vector实例对应的不同扩容行为
3.为什么Vector是线程安全的,而ArrayList、LinkedList是线程不安全的?
首先贴出Vector源码(源码出自jdk1.8.0_191)
1.Vector类的成员变量的含义
elementData:用于存储数据的数组,Vector集合中包含的数据个数就是该数组的长度capacityIncrement:每次Vector数组容量不够时,一次扩容的容量。而且仅当capacityIncrement为正数的时候,我们自定义的每次扩容数量才生效。若capacityIncrement不指定(不指定默认为0),指定为0,甚至指定为负数,那么我们自定义的每次扩容数量不会生效,换言之每当容量不够时,扩容后的容量是原容量的2倍(每次扩容100%)。如果我说的不形象那么请继续往下看,后面会以简单的例子详细解释这个结论。
elementCount:该变量的含义很抽象,但是该变量的值很有实际意义。我们看注释解释的:索引0~索引elementCount-1的位置是实际存储数据的位置。其实该索引范围也是对集合操作的合理索引范围,超过该索引范围的任何操作(增删改查)都会抛出异常或者错误。为什么这样说呢?请看下面一个例子,不过在看这个例子之前,我们先来看一下Vector类的构造方法
public Vector() {
this(10);
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
public Vector(Collection extends E> c) {
elementData = c.toArray();
elementCount = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}
可以看出Vector类给出了4个构造方法,但实际上只有两个构造方法。无论是调用无参数构造方法
public Vector()还是调用包含一个形参的构造方法public Vector(int initialCapacity),最后都是调用了public Vector(int initialCapacity, int capacityIncrement)构造方法。不同的是,调用无参数构造方法public Vector()最终会指定elementData数组容量为10;capacityIncrement=0,Vector容量不够的时候,只会2倍2倍地扩容。调用包含一个形参的构造方法public Vector(int initialCapacity)允许我们在初始化Vector的时候规定它的初始容量,Vector容量不够的时候,也只会2倍2倍地扩容。调用public Vector(int initialCapacity, int capacityIncrement) 构造方法不仅允许我们在初始化Vector的时候规定它的初始容量。Vector容量不够的时候,我们还可以规定每次增加几个容量,而不像前面的两个构造方法那么死板的2倍扩容方式。
看完了构造方法,我们来看一下之前提到的例子
Vectorvector=new Vector<>(5); for(int i=1;i<=3;i++){ vector.add(i); System.out.print("第"+i+"次添加元素时,vector.size()="+vector.size()+"t"); System.out.println("vector.capacity()="+vector.capacity()); } System.out.println(vector.get(4)); vector.add(4,4);
首先我们明确size()方法返回是Vector集合中有几个元素,而capacity()方法返回的是当前Vector集合的容量(即当前集合最多能装多少个元素)
我们来看控制台异常信息
Vector
原来是索引值不在0~elementCount-1的范围之内
所以说正如官方对成员变量elementCount注释解释的那样:索引0~索引elementCount-1的位置是实际存储数据的位置。该索引范围也是对集合操作的合理索引范围,超过该索引范围的任何操作(增删改查)都会抛出异常或者错误。
2.使用不同构造方法创建Vector实例对应的不同扩容行为
我们再看下面这个例子
public static void main(String[] args) {
Vector vector=new Vector<>();
for(int i=1;i<=11;i++){
vector.add(i);
System.out.print("第"+i+"次添加元素时,vector.size()="+vector.size()+"t");
System.out.println("vector.capacity()="+vector.capacity());
}
}
可见,调用无参数构造方法public Vector(),初试容量为10,Vector容量不够的时候,2倍2倍地扩容。
为什么是2倍2倍地扩容呢?
第一步:调用无参数构造方法,该方法调用有参构造方法public Vector(int initialCapacity)
参入参数10
public Vector() {
this(10);
}
第二步:该方法内部调用public Vector(int initialCapacity, int capacityIncrement)构造方法
传入参数10、0
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
第三步:该方法将elementData指定为容量为10的数组,扩容因子指定为0
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
第四步:调用add方法,该方法内部调用ensureCapacityHelper方法检查是否有溢出风险
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
第五步:如果minCapacity > elementData.length 即有溢出风险则调用grow函数进行扩容
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);
}
我们重点来看一下grow函数,首先明确oldCapacity是当前集合中已有元素的数量。
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity);
如果capacityIncrement > 0,那么扩容后elementData数组容量newCapacity=oldCapacity+capacityIncrement.
若capacityIncrement <=0,那么扩容后elementData数组容量newCapacity=oldCapacity*2
下面我们再来看一个例子
public static void main(String[] args) {
Vector vector=new Vector<>(5,1);
for(int i=1;i<=11;i++){
vector.add(i);
System.out.print("第"+i+"次添加元素时,vector.size()="+vector.size()+"t");
System.out.println("vector.capacity()="+vector.capacity());
}
}
我们指定Vector初始容量为5,capacityIncrement=1看看控制台不是不我们之前分析的那样
果然。我们初始化时指定扩容因子 capacityIncrement=1,每次容量不够的时候都只增加1个容量。
如果我们指定capacityIncrement为负数呢?继续看下面的例子
Vectorvector=new Vector<>(7,-3); for(int i=1;i<=11;i++){ vector.add(i); System.out.print("第"+i+"次添加元素时,vector.size()="+vector.size()+"t"); System.out.println("vector.capacity()="+vector.capacity()); } }
与我们之前分析的结论一致
如果我们使用构造方法public Vector(Collection extends E> c)创建Vector后,初始容量与扩容行为是不是与之前有所不同呢?我们直接看例子
public class OutOfMermory {
public static void main(String[] args) {
Vector vector=new Vector<>(7,-3);
for(int i=1;i<=11;i++){
vector.add(i);
// System.out.print("第"+i+"次添加元素时,vector.size()="+vector.size()+"t");
// System.out.println("vector.capacity()="+vector.capacity());
}
Vectorvector1=new Vector<>(vector);
System.out.println("vector1.size()="+vector1.size()+"t"+"vector1.capacity()="+vector1.capacity());
vector1.add(12);
System.out.println("vector1.size()="+vector1.size()+"t"+"vector1.capacity()="+vector1.capacity());
}
}
可见在这种情况下,Vector初始容量为传入集合中包含的元素的个数,之后容量不够的时候,也是2倍2倍地扩容
3.为什么Vector是线程安全的,而ArrayList、LinkedList是线程不安全的?
先说一下什么是线程不安全:线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。 List接口下面有两个实现,一个是ArrayList,另外一个是vector。 从源码的角度来看,因为Vector的方法前加了,synchronized 关键字,也就是同步的意思,因此Vector是线程安全的,而ArrayList类与LinkedList中的方法没有加锁.虽然线程不安全但是多线程访问该类效率明显高于Vector。 以向集合中添加元素为例,首先看一下ArrayList与Vector类中的add()方法源码
//arrayList类add方法源码
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//Vector类add方法源码
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
一个 ArrayList ,在添加一个元素的时候,它可能会有两步来完成:
在elementData[size]数组位置添加元素
size++数组容量加1
在单线程运行的情况下,如果 size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意,我们假设的是添加一个元素是要两个步骤,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。 那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。



