Vector源码详解

简介: Vector源码详解

一、介绍

前面我们介绍了java集合中的两种List实现类:基于数组实现的ArrayList和基于双向链表实现的LinkedList,从这两个类的源码中我们注意到,他们的方法实现都是线程不安全的,在多个线程共享一个实例的情况下会出现无法解决的问题。那么我们该如何避免问题的发生以保证线程安全呢?

java为我们提供了一些解决方案:

  • 使用 Collections.synchronizedList(List<T> list)方法,该方法对传入的List对象进行包装,返回一个线程安全的List。保证线程安全的原理就是在我们调用add()remove()等方法时通过使用synchronized代码块对方法进行包装,从而实现线程安全。

    // 线程不安全的List对象
    List<Integer> unSynchronizedList = new ArrayList<>();
    // 线程安全的List对象
    List<Integer> synchronizedList = Collections.synchronizedList(unSynchronizedList);
    
  • 使用CopyOnWriteArrayList,顾名思义,该类采用写入时复制的方法并结合ReentrantLock可重入锁来实现线程安全。

    // 线程安全的List对象
    List<Integer> list = new CopyOnWriteArrayList<>();
    
  • 使用Vector,本篇文章的主角,通过使用synchronized关键字实现线程安全。

    // 线程安全的List对象
    List<Integer> list = new Vector<>();
    

在java的早期版本中,为了解决ArrayList的并发问题,在java1.2中就开始引入了Vector,但是由于synchronized关键字过于重量级且不可控制,容易导致多线程死锁等问题的发生,相当于伤敌一千自损八百,捡了西瓜丢了芝麻。因此从java1.5开始,引入新的保证线程安全的集合实现类CopyOnWriteArrayList

虽然java已经不建议我们使用Vector了,但是由于使用简单,又有哪位同学在并发学习中选择直接绕过它呢?

下面我们看一下Vector的UML图:

继承关系图.png

继承关系图

① 实现了List接口,表示实现了List接口所定义的规范

② 继承自AbstractList抽象类,AbstractList同时也实现了List接口,表示继承了AbstractListList默认实现

① 实现了Cloneable接口,表示具有克隆的功能

② 实现了Serializable接口,表示具有序列化的功能

③ 实现了RandomAccess接口,表示具有随机访问的功能

从UML图中可以看出,Vector与ArrayList的继承关系完全一致。因此我们可以先假设这两个类的功能在使用上也完全一致,但要注意的是Vector是线程安全的,而ArrayList是线程不安全的。

注意:上面提到Vector是为了解决ArrayList线程不安全的问题而引入的,我们可以认为Vector是线程安全的ArrayList,在下面的学习中,我们应当将这两个类的不同点作为学习重点。

关于ArrayList源码的学习,请看往期文章:ArrayList源码

二、成员变量

// 对象数组,具有实际意义,用于保存Vector的元素,由此说明Vector是通过数组保存元素的
protected Object[] elementData;
// elementData中对象的数量
protected int elementCount;
// elementData数组在扩容时的增量
protected int capacityIncrement;


// 继承父类AbstractList的变量
// 在使用迭代器遍历过程中,对结构修改的次数,通过该字段可以实现fail-fast快速失败
protected transient int modCount = 0;

三、构造函数

①指定初始容量和扩容增量

// 该构造函数指定Vector实例的初始容量和扩容时的容量的增量
public Vector(int initialCapacity, int capacityIncrement) {
   
   
    // 调用父类的无参构造空方法,
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity];
    this.capacityIncrement = capacityIncrement;
}

②指定初始容量

// 指定初始容量,增量为缺省值0。在扩容部分我们可以看到,当增量为0时,实际增量为原始容量,即扩容后的容量为扩容前的2倍
public Vector(int initialCapacity) {
   
   
    this(initialCapacity, 0);
}

③无参构造

// 默认初始容量为10,增量为缺省值0
public Vector() {
   
   
    this(10);
}

④通过一个集合构造

// 该构造函数与ArrayList的构造函数的实现一致
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);
    }
}

四、扩容原理

从Vector的源码来看扩容原理,可以发现它的实现与ArrayList的扩容实现几乎完全一致,唯一不同的地方我把源码贴在下面:

// ArrayList的扩容实现, 扩容后的容量为扩容前容量的1.5倍
private void grow(int minCapacity) {
   
   
    // ...
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // ...
}

// Vector的扩容实现
// 如果在调用构造函数时指定的扩容增量大于0,则扩容后的容量=扩容前容量+扩容增量
// 否则,扩容后的容量=扩容前容量*2
private void grow(int minCapacity) {
   
   
    // ...
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
    // ...
}

五、补充说明

Vector从源码上来看,无论是增加元素、删除元素、修改元素、获取元素等方法,与ArrayList大同小异,几乎没有差别,建议读者在学习时以ArrayList作为重点去学习它的源码,然后再以它为参考,对Vector有个基本了解就行了。

再说说面试,Vector的重点知识其实很少很少,一般面试时面试官是不会问的,那么如果问了,可能会找出哪些问题?

①Vector和ArrayList的相同点是什么,不同点是什么,各自的使用场景是什么。

②以Vector线程安全为引子,对synchronized关键字的原理大文特问。

六、Vector和ArrayList的主要对比

相同点 不同点
底层结构 数组
初始容量 10
构造函数 1.都可以指定初始容量构造
2.都可以通过集合构造
1.ArrayList有三个,Vector有四个
2.ArrayList可以通过集合、初始容量构造;Vector可以通过集合、初始容量、扩容增量构造
扩容 ArrayList扩容后的容量为扩容前容量的1.5倍
Vector扩容后的容量为 扩容前容量+扩容增量扩容前容量*2
相关文章
|
数据处理 开发工具 C++
|
存储 关系型数据库 PostgreSQL
|
存储 移动开发 监控
微信支付开发避坑指南
【9月更文挑战第11天】在进行微信支付开发时,需遵循官方文档,确保权限和参数配置正确。开发中应注重安全,验证用户输入,合理安排接口调用顺序,并处理异常。上线后需实时监控支付状态,定期检查配置,关注安全更新,确保系统稳定运行。
325 3
|
Arthas 网络协议 Java
java instrument机制分析与实践
1. 原理介绍java instrument是一种字节码增强技术,在jdk1.5开始已引入,其核心功能实现依赖java.lang.instrument.Instrumentation接口,通过实现该接口,我们可以对已加载和未加载的类进行修改。java instrumentation最常用的一种使用方式是通过jvm的启动参数:-javaagent来启动,例如:java -javaagent:myag
1740 0
java instrument机制分析与实践
|
Java Maven Spring
用Spring导致的无法运行Java文件的问题的解决方案
本文提供了解决在IntelliJ IDEA社区版中使用Spring Initializr插件创建Spring项目后,Java文件无法运行的问题的方法,主要是通过加载Maven项目来解决。
260 0
|
JavaScript 应用服务中间件 Linux
宝塔面板部署Vue项目、服务端Node___配置域名
本文介绍了如何使用宝塔面板在阿里云服务器上部署Vue项目和Node服务端项目,并配置域名。文章详细解释了安装宝塔面板、上传项目文件、使用pm2启动Node项目、Vue项目打包上传、以及通过Nginx配置域名和反向代理的步骤。
4648 1
宝塔面板部署Vue项目、服务端Node___配置域名
|
JavaScript 前端开发
js 操作数组的方法
js 操作数组的方法
315 4
|
JavaScript 开发工具 git
Three.js第1篇,Three.js新手教学,如何在项目中使用Three.js(three.js使用流程详细,three.js的使用方式,three.js创建3d物体)
Three.js封装了WebGL的底层细节,是一款运行在浏览器中的 3D 引擎,可以用它创建各种三维场景,包括了摄影机、光影、材质等各种对象,目前在Git上已经拥有90k+的star。
648 0
Three.js第1篇,Three.js新手教学,如何在项目中使用Three.js(three.js使用流程详细,three.js的使用方式,three.js创建3d物体)
|
消息中间件 JavaScript Java
消息队列 MQ产品使用合集之如何嵌入到Spring Boot中运行
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。
|
Unix Linux 索引
Linux 基础解惑:Linux 下文件描述符标志和文件描述符状态标志,文件状态标志,文件状态之间的区别
Linux 基础解惑:Linux 下文件描述符标志和文件描述符状态标志,文件状态标志,文件状态之间的区别
439 0