public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
从上面的构造器中可以看出,如果调用无参构造器,则会创建一个初始化容量为10
,扩容容量为0
的Vector
集合。
1.2 如何扩容
Vector
的扩容机制和ArrayList
的很像,如果不清楚ArrayList
的扩容机制,可以看看[这篇文章](()。这里我们直接看Vector
的扩容方法grow
。
private void grow(int minCapacity) {
// overflow-conscious code
// 初始化数组的长度,默认为10
int oldCapacity = elementData.length;
// 是否指定扩容容量,不指定扩容为原来的2倍
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);
}
通过上面的方法,我们可以看出,如果指定了扩容容量的大小则扩容的新 《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 数组大小为原来的数组加上扩容容量的大小,如果不指定扩容容量的大小则扩容的新数组大小为原来数组大小的2
倍。这样扩容为原来的2倍是很消耗空间的,这也是Vector
被弃用的原因之一。
除此之外,看过源码的同学可能发现了,Vector
集合的所有操作元素的方法都加了synchronized
关键字,这就导致了操作Vector
的效率会非常低,在开发中,往往读操作的使用频率会远高于其他操作,而CopyOnWriteArrayList
就是这样一种读操作效率远高于写操作效率的List,一起来看看。
二、CopyOnWriteArrayList源码简析
CopyOnWriteArrayList
类图:
2.1 CopyOnWrite思想
CopyOnWrite
简称COW,根据名字来看就是写入时复制。意思就是大家共同去访问一个资源,如果有人想要去修改这个资源的时候,就需要复制一个副本,去修改这个副本,而对于其他人来说访问得资源还是原来的,不会发生变化。
2.2 初始化CopyOnWriteArrayList
CopyOnWriteArrayList
底层是也是有数组实现的。 本文我们只解读添加元素和读取元素的区别,删除修改元素原理和添加元素差不多,操作时都需要进行加锁,而读操作不会加锁。
CopyOnWriteArrayList
主要有以下两个变量:
// 独占锁
final transient ReentrantLock lock = new ReentrantLock();
// 存放元素的数组
private transient volatile Object[] array;
我们仔细来分析一下上面两个属性,这两个思想是 CopyOnWriteArrayList
的核心 。
- lock:ReentrantLock,独占锁,多线程运行的情况下,只有一个线程会获得这个锁,只有释放锁后其他线程才能获得。
- array:存放数据的数组,关键是被
volatile
修饰了,被volatile
修饰,就保证了可见性,也就是一个线程修改后,其他线程立即可见。
最常用的初始化方式如下:
/**
- Creates an empty list.
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
/**
- Sets the array.
*/
final void setArray(Object[] a) {
array = a;
}
初始化只是创建了一个空的数组,并将array
指向它。
2.3 添加元素
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 获取原来的数组
Object[] elements = getArray();
// 原来数组的长度
int len = elements.length;
// 创建一个长度+1的新数组,并将原来数组的元素复制给新数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 元素放在新数组末尾
newElements[len] = e;
// array指向新数组
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
添加数组的步骤如下:
- 获得独占锁,将添加功能加锁
- 获取原来的数组,并得到其长度
- 创建一个长度为原来数组长度+1的数组,并拷贝原来的元素给新数组
- 追加元素到新数组末尾
- 指向新数组
- 释放锁
这个过程是线程安全的,COW的核心思想就是每次修改的时候拷贝一个新的资源去修改,add()
方法再拷贝新资源的时候将数组容量+1,这样虽然每次添加元素都会浪费一定的空间,但是数组的长度正好是元素的长度,也在一定程度上节省了扩容的开销。
2.4 获取元素
public E get(int index) {
return get(getArray(), index);
每次添加元素都会浪费一定的空间,但是数组的长度正好是元素的长度,也在一定程度上节省了扩容的开销。
2.4 获取元素
public E get(int index) {
return get(getArray(), index);