下面学习一下Java中的List结构。
在List家族中,我们主要使用的就是LinkedList,ArrayList,和Vector三个主要的容器。
一、LinkedList和ArrayList的区别
从上面的继承图上来看,ArrayList只是实现了List接口,而LinkedList则是实现了List和Deque两个接口。
LinkedList的本质是一个双向链表。也可以把它当成为队列使用,还可以当成为栈来使用(Deque代表双端队列,也就是说可以对队列的双端进行操作。
)
对于ArrayList来说,其底层使用的是数组。这样子,如果我们需要在特定的位置添加数据的话就需要移动大量的数据,并且还有可能会面临扩容的情况,这个时候就性能就不够好了。但是对于获得index的元素的话则性能是相当好的。
对于LinkedList来说我对于特定的插入情况下,我们
可以判断是应该从链表的表头开始还是从表尾开始遍历。然后找到元素后就将其插入,这个时候插入的系统开销是会比ArrayLsit小一些的。但是对于要获得index位置的元素的话则性能就比ArrayList差的多了。
ArrayList 和 LinkedList的适用场景
总的来说ArrayList的性能是优于LinkedList,适用的场景也是比较多。但是当我们需要执行大量的add 或者对头和尾进行许多操作。并且对于get(index)方法的执行次数不高的话。则可以适用LinkedList,其他情况则使用ArrayList比较好。
二、ArrayList与Vector
1、序列化差异
ArrayList和Vector这两个集合类本质上没有太大的不同,他们都是实现了List接口。并且都是用来保存底层的java数组的。但是我们看看其中对于数组这一块的定义。
Vector:
protected Object[] elementData;
ArrayList:
transient Object[] elementData; /
这说明了ArrayList中的数据集合是不能够直接被序列化的。而ArrayList中提供了writeObject readObject两个方法来定制序列化。
而Vector是直接可以序列化的,并且它只是提供了writeObject方法而已,所以并不能够定制序列化。
从序列化的角度来看ArrayList比Vector更加安全,因为其提供了自定义序列化方式,所以不会很容易被人家解密。
2、线程安全
除此之外就是Vector中每一个方法都有
synchronized
修饰,所以我们可以将Vector当成为ArrayList的线程安全版本。
3、空间扩容
对于ArrayList的构造方法来说,其可以传递一个初始的大小,如果空间不足的话就可以按照原来的
1.5倍扩充容量。
public ArrayList(int initialCapacity)
而对于Vector来说,除了我们可以指定
空间大小之外我们还可以指定
增长的大小。也就是每次扩容的时候容量就会增加capacityIncrement,如果我们将增长大小指定为0的话则增长的情况就与ArrayList一样,空间不足的时候就会扩容1.5倍。
public Vector(int initialCapacity, int capacityIncrement)
4、比较&选择
由上面我们可以知道ArrayList不仅是序列化安全的,同时可用性等都会比Vector好。ArrayLsit已经基本替代了Vector了。而Vector的唯一有点就是线程安全。而我们也可以通过Collections工具类的synchronizedList方法得到一个线程安全的ArrayList
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
我们可以看到这个提供线程安全的ArrayList的方法其实就是通过原有的数据来生成一个线程安全的ArrayList。
static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> {
private static final long serialVersionUID = -7754090372962971524L;
final List<E> list;
SynchronizedList(List<E> list) {
super(list);
this.list = list;
}
SynchronizedList(List<E> list, Object mutex) {
super(list, mutex);
this.list = list;
}
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);}
}
public int indexOf(Object o) {
synchronized (mutex) {return list.indexOf(o);}
}
public int lastIndexOf(Object o) {
synchronized (mutex) {return list.lastIndexOf(o);}
}
public boolean addAll(int index, Collection<? extends E> c) {
synchronized (mutex) {return list.addAll(index, c);}
}
public ListIterator<E> listIterator() {
return list.listIterator(); // Must be manually synched by user
}
public ListIterator<E> listIterator(int index) {
return list.listIterator(index); // Must be manually synched by user
}
public List<E> subList(int fromIndex, int toIndex) {
synchronized (mutex) {
return new SynchronizedList<>(list.subList(fromIndex, toIndex),
mutex);
}
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
synchronized (mutex) {list.replaceAll(operator);}
}
@Override
public void sort(Comparator<? super E> c) {
synchronized (mutex) {list.sort(c);}
}
private Object readResolve() {
return (list instanceof RandomAccess
? new SynchronizedRandomAccessList<>(list)
: this);
}
}
我们可以看到其中所谓的线程安全ArrayList就是实现了Lis接口
,并且将所有的操作都变成一个互斥的信号量来控制。也就能够确保线程安全了。
三、Stack
另外还有基于Vector的Stack。
我们会发现Stack只是在Vector上面添加了5个栈操作的方法而已。其本质上还是一个Vector。并且我们可以看到Stack中的每一个方法都有synchronized 修饰,因为Vector是线程安全的,所以Stack也是线程安全的。
实际上即使程序需要使用栈这种数据结构的话java也不再推荐使用Stack,而是推荐实现了Deque接口的ArrayDeque来替代Stack。
但只是在无需保证线程安全的情况下才推荐使用。因为自带的Stack是考虑到线程安全的,所以我们在对线程安全不要求的情况下其性能是比较差的。