您的位置:首页 > 编程语言 > Java开发

常见Java集合实现细节——ArrayList和LinkedList

2017-04-16 23:55 337 查看

3、ArrayList和LinkedList

       在List集合的实现类中,主要有三个实现类:ArrayList、Vector、LinkedList。其中Vector还有一个Stack子类,这个Stack子类仅在Vector父类的基础之上增加了五个方法,这五个方法使得Vector扩展成为Stack。
//stack源代码
public class Stack<E> extends Vector<E> {
public Stack() {
}

public E push(E item) {
addElement(item);

return item;
}

public synchronized E pop() {
E       obj;
int     len = size();

obj = peek();
removeElementAt(len - 1);

return obj;
}

public synchronized E peek() {
int     len = size();

if (len == 0)
throw new EmptyStackException();
return elementAt(len - 1);
}

public boolean empty() {
return size() == 0;
}

public synchronized int search(Object o) {
int i = lastIndexOf(o);

if (i >= 0) {
return size() - i;
}
return -1;
}

}
        从上面的代码可以看出,Stack本质依然是一个Vector,并且是一个线程安全的类,因此Vector也是一个线程安全的类,只是增加了五个使用synchronized修饰的方法。
       实际上,即使程序中需要这种数据结构,Java也不推荐使用Stack类,而是推荐使用Deque实现类。从JDK1.6开始,Java提供了一个Deque接口,并为该接口提供了一个ArrayDeque实现类。在无须保证线程安全的情况之下,程序中完全可以使用ArrayDeque来代替Stack类。Deque接口代表双端队列的数据结构。双端队列已经不再是简单的队列,它既具有队列的性质(FIFO),也具有栈的性质(FILO)。

3、1 Vector与ArrayList区别

       Vector和ArrayList这两个集合类的本质并没有太大的却别,它们都实现了List接口,而且底层都是基于Java数组来存储集合元素的。
       从序列化机制的角度来看,ArrayList的实现比Vector的实现更安全。除此之外,Vector其实就是ArrayList的线程安全版本,ArrayList和Vector绝大部分的方法实现都是相同的,只是Vector的方法增加了synchronized进行修饰。除此之外,Vector是一个非常古老的集合,从JDK1.0就开始存在了,那时候它已经包含了大量从操作集合元素的方法,但是这些方法都具有方法名冗长、难于记忆的特点。随着JDK1.2增加了List接口之后,Vector改为实现了List接口,因此Vector将原有的方法进行了包装,包装成了遵循List接口规范的新方法。
       由于Vector包含的方法比ArrayList更多,而且ArrayList的序列化实现比Vector的序列化实现更安全,因此Vector基本上已经被ArrayList所代替。Vector唯一的好处就是线程安全。
提示:即使需要在多线程的环境下使用List集合,而且需要保证List集合的线程安全,则应该考虑将ArrayList包装成线程安全的集合类。Java提供了一个Collections工具类,通过该工具类的synchronizedList方法即可将一个普通的ArrayList包装成一个线程安全的ArrayList。

3、2 ArrayList与LinkedList区别

       List代表了一种线性表的数据结构,ArrayList则是一种顺序存储的线性表,底层是采用数组来保存每一个集合元素,LinkedList则是一种链式存储的线性表,其本质上就是一个双向链表,但它不仅实现了List接口,还实现了Deque接口。也就是说,LinkedList既可以当成双向链表使用,也可以当成队列使用,还可以当成栈来使用(Deque代表双端队列,
既具有队列的特征,也具有栈的特征)。
       ArrayList底层采用一个数组来保存所有的集合元素,因此在插入元素时要完成下面两件事情。

保证底层封装的数组长度大于集合元素的个数。
将插入位置之后的所有数组元素整体向后移动一个位置。

       反过来,当删除ArrayList集合中指定位置的元素时,程序也要进行整体移动,而且还需要将被删除索引的地方的数组元素赋为null。因此,对于ArrayList集合来说,当程序向其中添加、删除元素时,ArrayList底层都要对数组进行整体的移动,所以性能非常差。但如果程序调用get(int index)方法来取出ArrayList集合中的元素时,性能和数组一样都非常的快。

       LinkedList本质上就是一个双向链表,因此它在添加集合元素时,只要对链表进行如图所示的操作即可添加一个新的节点,因此可以非常方便地在指定节点之前插入新节点。如果只是单纯地添加某个节点,那么LinkedList的性能会非常的好;但如果需要向指定索引处添加节点,LinkedList必须先找到指定索引处的节点——这个搜索过程的系统开销并不小。



       但是LinkedList取出指定的元素就比较麻烦,必须逐个元素地搜索,直到找到第index个元素为止,因此对于系统的开销也比较大。

3、3 ArrayList与LinkedList性能分析及适用场景

       当程序需要以get(int index)方法获取List集合指定索引处的元素时,ArrayList性能大大地由于LinkedList。因为ArrayList底层以数组来保存集合元素,所以调用该方法获取指定索引处的元素时,底层实际上调用elementData[index]来返回元素,因此性能非常好。而LinkedList则必须逐个地搜索。
       当程序调用add(int index , Object obj)方法向List集合中添加元素时,LinkedList性能高于ArrayList。因为ArrayList必须对底层数组元素进行整体移动。如果添加元素导致集合长度超过了底层数组长度,ArrayList必须创建一个长度为原来长度1.5倍的数组,再由垃圾回收机制回收原有数组,因此系统开销比较大。对于LinkedList而言,它的主要开销集中在entry(int
index)方法,该方法必须逐个地搜索,直到找到index处的元素,然后在该元素之前插入新元素。
       当程序调用remove(int index)方法删除index索引出的元素时,ArrayList同样也需要对底层数组进行整体移动,但调用remove(int index)删除集合元素时,ArrayList无须考虑创建新数组,因此执行该方法比ArrayList执行add(int index , Object obj)方法略微快一点。当LinkedList调用remove(int
index)方法删除集合元素时,与调用add(int index , Object obj)开销几乎相同。
       当调用add(Object obj)方法向List集合尾端添加一个元素时,大部分时候ArrayList无须对底层数组元素进行整体移动,因此也可以获得很好的性能(甚至比LinkedList的性能更好);但如果添加这个元素导致集合长度超过了底层数组长度,ArrayList必须创建一个长度为原来长度1.5倍的数组,再由垃圾回收机制回收原有数组,这样系统开销就比较大了。但LinkedList调用add(Object
obj)方法添加元素时总可以获得很好的性能。
       当程序把LinkedList当成双端队列、栈使用时,LinkedList可以快速定位需要操作的元素,因此LinkedList总是具有更好的性能表现。
       总而言之,大部分情况之下,ArrayList的性能总是优于LinkedList,因此大部分情况下都应该优先考虑ArrayList集合。但是如果程序经常需要添加、删除元素,尤其是经常需要调用add(E
e)方法向集合中添加元素,则应该考虑使用LinkedList集合。

       
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息