Java ArrayList vs. Vector

ArrayList 和 Vector 的比较是面试中一个常见的问题,要想回答得好,还需要下点工夫,本文来总结下它们之间的相似以及区别。

相似点

先看下相似点。首先我们都是由数组来实现的,并且它们都实现了 RandomAccess 接口,因此支持快速随机访问,也就是通过索引快速定位元素。因此从本质上来说,它们之间是可以能通用的。

区别

Vector 现在很少用到,这是因为它与 ArrayList 之间的区别造成的。

线程安全

Vector 是线程安全的类,因为它的大部分方法都是通过 synchronized 关键字进行同步的,例如 add() 方法

    public synchronized boolean add(E e) {
		// ...
        return true;
    }

然而 ArrayList 并不是线程安全的类,它的方法没有 synchronized 关键字,也没有通过锁来保证线程安全。

大部分时候,我们都不需要考虑线程安全,因此我们经常使用 ArrayList 而不是 Vector ,毕竟同步是需要付出一定的性能代价的。然而如果一旦考虑线程安全问题,我们还是可以使用 Vector 的。

虽然 ArrayList 不是线程安全的,但是我们还是有办法使之成为线程安全,例如我们可以在某个对象上进行同步,然后再操作 ArrayList 对象,例如下面的添加元素的操作

synchronized (object) {
	arraylist.add("element");
}

但是这似乎显得比较笨拙,我们还可以在创建 ArrayList 对象时,使用 Collections.synchronizedList() 来创建一个 ArrayList 的包装类对象

List list = Collections.synchronizedList(new ArrayList());

这样是不是就方便很多。

ArrayList 还有一个线程安全的版本 CopyOnWriteArrayList,它的修改方法,例如 add(), set() 都是在底层数组的拷贝上进行的,但是 CopyOnWriteArrayList 通常用于遍历操作的数量远远大于修改的数量的情况下。

扩容方式

首先看下 ArrayList 的扩容方式

    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        // 扩容0.5倍大小
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

ArrayList 的基本扩容策略是先扩容0.5倍,然后再根据实际需要进行调整。

再来看下 Vector 扩容方式

    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        // 使用自定义扩容方式或者扩容1倍
        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);
    }

capacityIncrement 表示扩容时的增长量,这个是在构造函数时赋值的,默认是0。

因此,Vector 的扩容策略是,如果指定的扩容的大小,那么就使用自定义的策略,否则扩容一倍。再根据需要进行调整。

通常,我们并不知道设置扩容量为多大,才最合适,因此都不会在构造函数中设置这个值,那么这个值默认就是0。因此 Vector 的基本扩容策略是扩容一倍。

我们并不能一概而论地说扩容策略谁优谁劣,要看实际情况而定的。

序列化

首先看下 ArrayList 的序列化

	// tansient 修饰的数组,默认不会执行序列化操作
	transient Object[] elementData;
	
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        int expectedModCount = modCount;
        // 执行默认的序列化操作
        s.defaultWriteObject();
		
		// 保存容量
        s.writeInt(size);
		
		// 保存数组元素
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

ArrayList 背后的数组被 transient 关键字修饰,那么它就不会执行默认序列化操作,因此在 writeObject() 方法中要手动进行保存操作,为什么这样操作呢?

因为数组中可以存在一些空元素,如果使用默认序列化机制来保存数组的元素,那么这些空元素是会被保存的。那么在反序列化时,会额外为这些空元素分配不必须的空间。如此看来,ArrayList 的序列化操作还是比较高效的嘛。

我们再来看下 Vector 的序列化操作

	protected Object[] elementData;
	
    private void writeObject(java.io.ObjectOutputStream s)
            throws java.io.IOException {
        final java.io.ObjectOutputStream.PutField fields = s.putFields();
        final Object[] data;
        synchronized (this) {
            fields.put("capacityIncrement", capacityIncrement);
            fields.put("elementCount", elementCount);
            data = elementData.clone();
        }
        fields.put("elementData", data);
        s.writeFields();
    }

首先我们注意到 Vector 背后的数组没有使用 transient 关键字修饰,但是它还是定义了 writeObject() 方法来自己处理序列化操作。可以看到,Vector 序列化,保存了数组的完整的克隆版本,其中就包括一些空元素。那么反序列化时,可以会浪费额外的空间。

因此在序列化问题上,ArrayList 可能比 Vector 更高效点。

结束

在平时的工作中,Vector 很少用到,但是并不是无用武之地,只是大部分时候我们不需要线程安全,因此 ArrayList 占据主流。并且在需要线程安全时,ArrayList 也提供了线程安全的方式,因此 Vector 渐渐退出历史舞台。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值