1. 概述
- 说到Vector,可能参加过面试,背过八股文的都知道Vector是ArrayList的、线程安全的替代类
- 具体来说,相对ArrayList,Vector使用
synchronized
关键字,实现了线程同步、保证了线程安全 - 但是,具体聊到Vector的细节,就不是很清楚了
- 还有,c++中也有Vector,具体有什么异同也不知道 😂
- 本文将以ArrayList为基础,有对比地学习Vector
1.1 类注释
Vector的类注释,翻译如下:
- Vector是动态数组,支持随机访问,支持扩容和缩容
- 其扩容,有一个参数
capacityIncrement
,为扩容时增加的容量 - 可以将
capacityIncrement
参数设置得大一点,避免频繁扩容 - 所谓缩容,其实ArrayList也有:
trimToSize()
方法,不过这是一个同步方法 😂
- 其扩容,有一个参数
- Vector使用fail-fast迭代器,支持Iterator和ListIterator两种迭代器,分别对应不会除法fail-fast机制的remove和add方法
- 和ArrayList的类注释比起来,信息量少多了
- 实际上,Vector也允许
null
值,是线程安全的list
总结:
- Vector实现了List接口,允许
null
值 - Vector是动态数组,支持随机访问、扩缩容;扩容时,可以增加固定容量
capacityIncrement
- Vector是线程安全的list类,通过synchronized关键字实现
- Vector使用fail-fast迭代器
1.2 类图
-
Vector类的声明如下,与ArrayList没任何区别
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
-
elementData字段不是transient的,可以参与序列化
-
但是,同样也提供了
writeObject()
和readObject()
方法,支持序列化和反序列化
1.3 成员变量
类常量
-
只有一个:
MAX_ARRAY_SIZE
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
-
相对ArrayList,缺少了默认容量
DEFAULT_CAPACITY
、共享空数组EMPTY_ELEMENTDATA
、具有默认容量的共享空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA
-
留个疑问: 没有默认的初始容量,构建Vector时必须指定初始容量?
实例变量
-
实例变量如下:
// 不再是transient,而是protected protected Object[] elementData; // 元素个数,等同于ArrayList中的size protected int elementCount; // 扩容时,每次增长的容量;如果增长量 <= 0,则每次扩容2倍 protected int capacityIncrement;
1.4 构造函数
-
Vector类,有4个构造函数
- 默认初始化容量为10,但并未定义成类变量
- 若未指定capacityIncrement,则默认为0,扩容时则为2倍扩容
// 创建Vector时,初始化容量和capacityIncrement public Vector(int initialCapacity, int capacityIncrement) { super(); // 对应AbstractList的more if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; this.capacityIncrement = capacityIncrement; } // 创建Vector时,指定初始化容量,capacityIncrement默认为0,将按照2倍进行扩容 public Vector(int initialCapacity) { this(initialCapacity, 0); } // 无参构造函数,初始化容量默认为10 public Vector() { this(10); } // 基于已有的集合构建Vector public Vector(Collection<? extends E> c) { Object[] a = c.toArray(); elementCount = a.length; if (c.getClass() == ArrayList.class) { elementData = a; } else { elementData = Arrays.copyOf(a, elementCount, Object[].class); } }
-
其中,
super();
将调用父类AbstractList的默认构造函数。该构造函数,do nothingprotected AbstractList() {}
2. 查找方法
get 方法
- 通过synchronized关键字,保证线程安全
- 直接进行下标越界检查,抛出的是
ArrayIndexOutOfBoundsException
异常,而非其父类IndexOutOfBoundsException
public synchronized E get(int index) { if (index >= elementCount) throw new ArrayIndexOutOfBoundsException(index); return elementData(index); }
indexOf 方法
-
实际调用的是,指定index的indexOf() 方法
public int indexOf(Object o) { return indexOf(o, 0); }
-
指定index的indexOf() 方法是线程安全的:从指定的index开始查找元素的位置,而非默认从0开始
public synchronized int indexOf(Object o, int index) { if (o == null) { for (int i = index ; i < elementCount ; i++) if (elementData[i]==null) return i; } else { for (int i = index ; i < elementCount ; i++) if (o.equals(elementData[i])) return i; } return -1; }
-
从上述代码可以看出,Vector允许
null
值
lastIndexOf 方法
-
实际调用的是指定index的lastIndexOf() 方法,也使用synchronized 关键字修饰
-
实际无需使用? 因为,指定index的lastIndexOf() 方法是线程安全的
public synchronized int lastIndexOf(Object o) { return lastIndexOf(o, elementCount-1); }
-
指定index的lastIndexOf() 方法:从指定的位置开始查找,而非默认从
size - 1
开始public synchronized int lastIndexOf(Object o, int index) { if (index >= elementCount) throw new IndexOutOfBoundsException(index + " >= "+ elementCount); if (o == null) { for (int i = index; i >= 0; i--) if (elementData[i]==null) return i; } else { for (int i = index; i >= 0; i--) if (o.equals(elementData[i])) return i; } return -1; }
3. 添加方法 & 扩容
3.1 add方法与扩容
不指定index的add方法
-
使用synchronized 关键字修饰,是线程安全的方法
-
通过
ensureCapacityHelper()
方法实现扩容public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; }
扩容的实现
- ensureCapacityHelper() 方法的代码如下:
- 直接比较指定容量与当前容量的大小,以决定是否扩容
- 没有了ArrayList中,通过
calculateCapacity()
更新指定容量的部分
private void ensureCapacityHelper(int minCapacity) { // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
- grow() 方法的代码如下:
- 若
capacityIncrement > 0
,则增加固定的容量;否则,扩容两倍 - 同时,也会使用指定容量和
MAX_ARRAY_SIZE
修正newCapacity
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); if (newCapacity - minCapacity < 0) // 批量添加时,指定容量可能大于newCapacity newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) // 扩容,导致溢出;或指定容量过大,导致溢出 newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); }
- 若
3.2 其他添加方法
指定index的add方法
-
直接调用线程安全的
insertElementAt()
方法实现public void add(int index, E element) { insertElementAt(element, index); }
-
insertElementAt()
方法- 具体实现与ArrayList指定index的add 方法差不多
- check下标是否越界,按需扩容,元素后移、空出位置,添加元素
public synchronized void insertElementAt(E obj, int index) { modCount++; if (index > elementCount) { throw new ArrayIndexOutOfBoundsException(index + " > " + elementCount); } ensureCapacityHelper(elementCount + 1); System.arraycopy(elementData, index, elementData, index + 1, elementCount - index); elementData[index] = obj; elementCount++; }
批量添加的方法
- 批量添加,也支持不指定index(在末尾添加)、指定index两种情况
- 都使用synchronized关键字修饰,都是线程安全的
- 具体实现,可以查看源码
4. 修改元素
4.1 修改元素的值
set 方法
-
与ArrayList中的方法差异不大,主要是多了synchronized 关键字,是线程安全的方法
public synchronized E set(int index, E element) { if (index >= elementCount) throw new ArrayIndexOutOfBoundsException(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
setElementAt 方法
-
setElementAt 方法与set方法的作用相同,但是Vector专门创建了该方法,可能有特殊的用途
- 此外,setElementAt 方法没有返回值,不会返回oldValue,而是直接修改
public synchronized void setElementAt(E obj, int index) { if (index >= elementCount) { throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount); } elementData[index] = obj; }
4.2 修改数组的size
-
在学习set方法时,发现还有一个setSize方法
public synchronized void setSize(int newSize) { modCount++; if (newSize > elementCount) { ensureCapacityHelper(newSize); } else { for (int i = newSize ; i < elementCount ; i++) { elementData[i] = null; } } elementCount = newSize; }
-
阅读源码后,总体感觉:这个方法很鸡肋
public static void main(String[] args) { Vector<Integer> vector = new Vector<>(); vector.add(1); System.out.println(vector); // 设置size,多余的空间被填充null元素 vector.setSize(3); System.out.println(vector); // 再添加元素,中间元素为null vector.add(4); System.out.println(vector); }
-
Vector中原本有1个元素,setSize 为3,这样出现两个
null
元素 -
再添加元素时,个数变成4;打印出来的结果,中间有
null
值,容易让人困惑
5. 删除方法
通过索引删除元素
-
与ArrayList的实现一致,多了synchronized 关键字,以保证线程安全
public synchronized E remove(int index) { modCount++; if (index >= elementCount) throw new ArrayIndexOutOfBoundsException(index); E oldValue = elementData(index); int numMoved = elementCount - index - 1; // if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--elementCount] = null; // Let gc do its work return oldValue; }
删除指定元素
- 与ArrayList的实现不同,它通过调用其他方法实现删除逻辑,并非在方法体里直接实现
- 实际调用
removeElement()
方法删除指定元素 removeElement()
方法:先获取元素的index,然后调用removeElementAt()
方法实现删除- 整个删除过程调用的三个方法,都是
public synchronized
的
public boolean remove(Object o) { return removeElement(o); } public synchronized boolean removeElement(Object obj) { modCount++; int i = indexOf(obj); if (i >= 0) { removeElementAt(i); return true; } return false; }
- 实际调用
批量删除
public synchronized boolean removeAll(Collection<?> c)
:调用祖父类AbstractCollection
的removeAll()public synchronized void removeAllElements()
:遍历数组,将元素置为null
public synchronized boolean removeIf(Predicate<? super E> filter)
:遍历数组,将满足过滤条件的元素置为null
public void clear()
:内部调用removeAllElements() 方法实现清空逻辑
6. 总结
看到有博客说:
- Vector从JDK 1.0开始出现,ArrayList从JDK 1.2开始出现
- 目前使用Vector的场景很少,但不应该不学习,说不定哪天就派上用场了
- 自己学习Vector,主要是想了解它与ArrayList的区别
Vector的重点知识
- 线程安全的类,通过synchronized 关键字,实现线程同步
- 无类变量
DEFAULT_CAPACITY
,默认容量直接写死为10(貌似不优雅🤔) - 扩容时,要么增加固定容量,要么扩容2倍
- 存在一些特有的方法,实际与已有方法的作用相同;甚至,已有方法内部调用的就是这些方法,来实现具体的逻辑
6.1 Vector与ArrayList的异同
相同点
- 均实现了List 接口,支持
null
值 - 动态数组,支持随机访问,支持动态扩容、缩容
- 均使用fail-fast迭代器,包括Iterator和ListIterator
不同点
- 扩容时,ArrayList一般扩容
1.5
倍,Vector扩容固定容量capacityIncrement
或者2
倍 - Vector是线程安全的,通过为方法增加synchronized 关键字,实现线程同步;ArrayList是非线程安全的,多线程环境下使用需要考虑转换或替换
- Vector依靠synchronized 关键字实现线程同步,开销比ArrayList大,访问速度更慢;在无需保证线程安全的情况下,建议使用ArrayList
参考文档
- Vector(短小精悍的学习笔记)
- JDK1.8 源码学习笔记
- Java小白集合源码的学习系列:Vector
- 【Java学习笔记】(十一)Vector 源码分析 (setSize方法的讲解)