这三者都是实现集合框架中的List,也就是所谓的有序集合,因此具体功能也比较近似,比如都提供按照位置进行定位、添加或者删除的操作,都提供迭代器以遍历其内容等。但因 为具体的设计区别,在行为、性能、线程安全等方面,表现又有很大不同。
简介
Vector是Java早期提供的线程安全的动态数组,如果不需要线程安全,并不建议选择,毕竟同步是有额外开销的。Vector内部是使用对象数组来保存数据,可以根据需要自动的增加 容量,当数组已满时,会创建新的数组,并拷贝原有数组数据。
public synchronized void addElement(E obj) { modCount++; add(obj, elementData, elementCount); }
里面基本都是加同步的,可见效率之低。
ArrayList是应用更加广泛的动态数组实现,它本身不是线程安全的,所以性能要好很多。与Vector近似,ArrayList也是可以根据需要调整容量,不过两者的调整逻辑有所区 别,Vector在扩容时会提高1倍,而ArrayList则是增加50%。
private Object[] grow(int minCapacity) { int oldCapacity = elementData.length; if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { int newCapacity = ArraysSupport.newLength(oldCapacity, minCapacity - oldCapacity, /* minimum growth */ oldCapacity >> 1 /* preferred growth */); return elementData = Arrays.copyOf(elementData, newCapacity); } else { return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)]; } }
LinkedList顾名思义是Java提供的双向链表,所以它不需要像上面两种那样调整容量,它也不是线程安全的。
场景
Vector和ArrayList作为动态数组,其内部元素以数组形式顺序存储的,所以非常适合随机访问的场合。除了尾部插入和删除元素,往往性能会相对较差,比如我们在中间位置插 入一个元素,需要移动后续所有元素。
而LinkedList进行节点插入、删除却要高效得多,但是随机访问性能则要比动态数组慢。
总之还是数组和链表的优缺点决定。
Java的集合框架,Collection接口是所有集合的根,然后扩展开提供了三大类集合,分别是:
List,也就是我们前面介绍最多的有序集合,它提供了方便的访问、插入、删除等操作。
Set,Set是不允许重复元素的,这是和List最明显的区别,也就是不存在两个对象equals返回true。我们在日常开发中有很多需要保证元素唯一性的场合。
Queue/Deque,则是Java提供的标准队列结构的实现,除了集合的基本功能,它还支持类似先入先出(FIFO, First-in-First-Out)或者后入先出(LIFO,Last-In-FirstOut)等特定行为。这里不包括BlockingQueue,因为通常是并发编程场合,所以被放置在并发包里。
queue在操作系统上应用场景很多,比如内存分配模型的queue实现。
这些集合类,都不是线程安全的,对于java.util.concurrent里面的线程安全容器。但是,并不代表这些集合完全不能支持并发编程的场景, 在Collections工具类中,提供了一系列的synchronized方法,比如:
List list = Collections.synchronizedList(new ArrayList<>());
jdk的实现
public boolean equals(Object o) { if (this == o) return true; synchronized (mutex) {return list.equals(o);} } public int hashCode() { synchronized (mutex) {return list.hashCode();} } public E get(int index) { synchronized (mutex) {return list.get(index);} } public E set(int index, E element) { synchronized (mutex) {return list.set(index, element);} } public void add(int index, E element) { synchronized (mutex) {list.add(index, element);} } public E remove(int index) { synchronized (mutex) {return list.remove(index);} }
它的实现,基本就是将每个基本方法,比如get、set、add之类,都通过synchronizd添加基本的同步支持,非常简单粗暴,但也非常实用。注意这些方法创建的线程安全集合,都 符合迭代时fail-fast行为,当发生意外的并发修改时,尽早抛出ConcurrentModifcationException异常,以避免不可预计的行为。
在Java 8之中,Java平台支持了Lambda和Stream,相应的Java集合框架也进行了大范围的增强,以支持类似为集合创建相应stream或者parallelStream的方法实现,我们可以 非常方便的实现函数式代码。
在Java 9中,Java标准类库提供了一系列的静态工厂方法,比如,List.of()、Set.of(),大大简化了构建小的容器实例的代码量。根据业界实践经验,我们发现相当一部分集合实例 都是容量非常有限的,而且在生命周期中并不会进行修改。但是,在原有的Java类库中,我们可能不得不写成:
ArrayLis<String> lis = new ArrayLis<>(); lis.add("Hello"); lis.add("World");
而利用新的容器静态工厂方法,一句代码就够了,并且保证了不可变性。
Lis<String> simpleLis = Lis.of("Hello","world");
更进一步,通过各种of静态工厂方法创建的实例,还应用了一些我们所谓的最佳实践,比如,它是不可变的,符合我们对线程安全的需求;它因为不需要考虑扩容,所以空间上更加 紧凑等。
如果我们去看of方法的源码,你还会发现一个特别有意思的地方:我们知道Java已经支持所谓的可变参数(varargs),但是官方类库还是提供了一系列特定参数长度的方法,看起 来似乎非常不优雅,为什么呢?这其实是为了最优的性能,JVM在处理变长参数的时候会有明显的额外开销,如果你需要实现性能敏感的API,也可以进行参考。
jdk的List.of()实现
static final class List12<E> extends AbstractImmutableList<E> implements Serializable { @Stable private final E e0; @Stable private final E e1; List12(E e0) { this.e0 = Objects.requireNonNull(e0); this.e1 = null; } List12(E e0, E e1) { this.e0 = Objects.requireNonNull(e0); this.e1 = Objects.requireNonNull(e1); } ... }
可参考
杨晓峰老师的《java核心技术》
https://medium.com/@gilangkusumajati/arraylist-vs-linkedlist-vs-vector-22e1721a66b0
https://www.programcreek.com/2013/03/arraylist-vs-linkedlist-vs-vector/