Vector,ArrayList,LinkedList的区别

这三者都是实现集合框架中的List,也就是所谓的有序集合,因此功能比较近似,比如都是按照位置进行定位,添加,删除的操作,都提供迭代器遍历集合的内容。但是因为具体的设计区别,它们三个在行为,性能,线程安全方面又有着截然不同的表现。

Vector是Java早期提供的线程安全的动态数组,如果不需要线程安全,不建议选择它。毕竟同步是有额外开销的。Vector内部是根据对象数组来保存数据的,并且根据需要自动增加数组的容量,当数组已满,会创建新的数组,并拷贝原有的数组,因为这些操作是在内部实现的,所以对于使用者来说是没什么感觉的。

ArrayList是应用更加广泛的动态数组实现的,它本身不是线程安全的,所以性能要好很多,与Vector类似,ArrayList也是可以自动调整容量的,不过两者调整的逻辑有些区别,Vector默认是在扩容时会提高1倍,ArrayList则是增加50%。

LinkedList是由Java提供的双向链表实现的,所以它不需要像上面需要扩容,同时它也不是线程安全的。

Vector和ArrayList作为动态数组,其内部元素是以数组形式存储的,所以非常适合随机访问的场合,除了尾部插入和删除元素,往往性能会比较差,比如我们要在中间插入一个元素,首先需要将这个元素的后面元素都向后移动1位,而LinkedList是进行节点插入,删除高效的多,但是随机访问性能要比动态数组慢的多。所以我们总结一下,当我们要操作的集合插入删除的机会大于查询的几率,选择LinkedList集合,反之,查询的几率大于插入,删除的几率,可以选择Vector,ArrayList的,同时又因为在不考虑线程安全的角度,Vector的效率不如ArrayList,所以我们使用的集合大多都是ArrayList的。

虽然ArrayList和LinkedList不是线程安全的,但并不代表这些集合完全不能支持并发编程的场景。在Collections工具类中,提供了一系列synchronized的方法。比如 
static <T> List<T> synchronizedList(List<T> list),我们完全可以利用类似的方法来实现基本的线程安全集合:

List list = Collections.synchronizedList(new ArrayList()); 它的实现基本就是将每个基本方法,比如get,set,add方法,都通过synchronized添加基本的同步支持,非常简单粗暴,但也非常实用,注意这些方法创建的线程安全集合,都符合迭代时fail-fast行为,当发生意外的并发修改时,尽早抛出ConcurrentModificationException 异常,以避免不可预计的行为。

接下来讲一下ArrayList的扩容原理:

ArrayList内部是用一个对象数组来存储数据的。提供插入数据的add()方法,在插入的过程中,判断当前的容量是否满足要求,不满足的话,就会扩容原来的1.5倍,把当前的数据拷贝到新创建的数组。

private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }

public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }

public void add(int index, E element) {
        rangeCheckForAdd(index);
        modCount++;
        final int s;
        Object[] elementData;
        if ((s = size) == (elementData = this.elementData).length)
            elementData = grow();
        System.arraycopy(elementData, index,
                         elementData, index + 1,
                         s - index);
        elementData[index] = element;
        size = s + 1;
    }

可以看出重构的add()方法中,在数组容量不足的情况,都会调用grow()方法

private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }

    private Object[] grow() {
        return grow(size + 1);
    }

在grow(int minCapacity)的方法中,我们可以看到调用Arrays的静态拷贝函数,先创建一个newCapacity(minCapacity)大小的对象数组,通过拷贝函数System.arraycopy把elementData 数组内容拷贝到新数组中返回。

private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity <= 0) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }
private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE)
            ? Integer.MAX_VALUE
            : MAX_ARRAY_SIZE;
    }

newCapacity(int minCapacity)这个函数才是把当前的数组容量扩大的原来的1.5呗,可以看到数组的容量如果大于MAX_ARRAY_SIZE的话,调用hugeCapacity(minCapacity)这个函数,而hugeCapacity这个函数返回的最大的长度是Integer.MAX_VALUE,那么是否也就是说ArrayList内部能够存储的最大字节是Integer.MAX_VALUE吗?我觉得最好把最大字节设置为MAX_ARRAY_SIZE,毕竟还要有8个字节来存储一个数组头的相关信息,一旦超过了MAX_ARRAY_SIZE,可能会出现某些不可预料的事情。

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

如果你有兴趣的朋友了解Hashtable,HashMap,TreeMap之间的区别,可以看我的下一篇文章。Hashtable,HashMap,TreeMap之间的区别

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值