Vector简单学习(JDK1.8)

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 nothing

    protected 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

参考文档

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值