zoukankan      html  css  js  c++  java
  • java集合的实现细节--ArrayList和LinkedList

      ArrayList和LinkedList的实现差异 

      List代表一种线性表的数据结构,ArrayList则是一种顺序存储的线性表,ArrayList底层采用动态数组的形式保存每一个集合元素,LinkedList则是一种链式存储的线性表,其本质上就是一个双向链表,它不仅实现了List接口,还实现了Deque接口,Deque代表了一种双端队列,既具有队列(FIFO)的特性,也具有栈(FILO)的特性,也就是说,LinkedList既可以当成双向链表使用,也可以当成队列使用,还可以当成栈来使用。

    public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

      ArrayList底层采用一个elementData数组来保存所有集合元素,因此ArrayList在插入元素时需要完成两件事情:

      ①、保证ArrayList底层封装的数组长度大于集合元素的个数

      ②、将插入位置之后的所有数组元素“整体搬家”,向后移动一“格”

      当删除ArrayList集合中指定位置的元素时,程序也要进行“整体搬家”,而且还需要将被删除索引处的数组元素赋值为null,下面是ArrayList集合的remove(int index)方法的源代码。

    public E remove(int index) {
            rangeCheck(index);
    
            modCount++;
            E oldValue = elementData(index);
    
            int numMoved = size - index - 1;
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            elementData[--size] = null; // clear to let GC do its work
    
            return oldValue;
        }

      对于ArrayList集合而言,当程序向ArrayList中添加、删除集合元素时,ArrayList底层都是需要对数组进行“整体搬家”,因此性能很差。

      但如果程序调用get(int index)方法取出ArrayList集合中的元素时,性能和数组几乎相同,效率非常快。下面是ArrayList集合的get(int index)方法的源代码。

      E elementData(int index) {
           return (E) elementData[index];
      }
         
      public E get(int index) {
            rangeCheck(index);
            return elementData(index);
        }    

      LinkedList本质上是一个双向链表,因此它使用内部类来保存每一个集合元素。

     private static class Node<E> {
         //集合元素 E item;
         //保存指向下一个链表节点的引用 Node
    <E> next;
         //保存指向上一个链表节点的引用 Node
    <E> prev;      //普通构造器 Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }

      从上面程序中的粗体字代码可以看出,一个Node对象代表双向链表的一个节点,该对象中的next变量指向下一个节点,previous子指向上一个节点。

      由于LinkedList采用双向链表来保存集合元素,因此它在添加集合元素时,只要对链表进行插入即可。下面是LinkedList添加节点的源码。

         public void add(int index, E element) {
            checkPositionIndex(index);
    
            if (index == size)
                linkLast(element);
            else
                linkBefore(element, node(index));
        }

      从上面代码看出,由于Linked本质上就是一个双向链表,因此它非常方便的在指定节点之前插入新节点。

      上面add(int index,E element)方法实现中用到了以下三个方法。

      ①、node(int index):搜索指定索引出的元素

      ②、linkLast(E e):将e新节点插入到最后

      ③、linkBefore(E e,Node<E> succ):在succ节点之前插入e新节点

      node(int index)实际上就是get(int index)方法的底层实现。Linked必须逐个元素的搜索,直到找到第index个元素为止。node(int index)采用二分法的方式进行查找,以下是该方法的源代码。

       Node<E> node(int index) {
            // assert isElementIndex(index);
         //如果index小于size/2
            if (index < (size >> 1)) {
                Node<E> x = first;
                for (int i = 0; i < index; i++)
                    x = x.next;
                return x;
            } else {
                Node<E> x = last;
                for (int i = size - 1; i > index; i--)
                    x = x.prev;
                return x;
            }
        }

      上面的node(int index)方法就是逐个元素的找到index索引处的元素,只是由于LinkedList是一个双向链表,因此程序先根据index的值判断它离链表头近还是离链表尾近,如果离链表头近则从头端开始搜索,如果离链表尾近则从尾端开始搜素。

      Linked的get(int index)方法只是对上面node(int index)方法的简单包装。get(int index)方法的源代码如下。

        public E get(int index) {
            checkElementIndex(index);
            return node(index).item;
        }

      但单纯的插入操作就比较简单了,只要修改几个节点里面的previous、next引用的值即可。下面是linkedbefore(E e, Node<E> succ)方法的源代码。

       void linkBefore(E e, Node<E> succ) {
            // assert succ != null;
            final Node<E> pred = succ.prev;
            final Node<E> newNode = new Node<>(pred, e, succ);
            succ.prev = newNode;
            if (pred == null)
                first = newNode;
            else
                pred.next = newNode;
            size++;
            modCount++;
        }

      如果只是单纯的添加某个节点,那么LinkedList的性能是非常好的,但如果需要向某个指定索引处添加节点,LinkedList必须先找到指定索引处的节点,这个搜索过程的系统开销并不小,因此LinkedList的add(int index,E e)方法的性能并不是特别好。如果只使用LinkedList的addFrist(E e)、addLast(E e)、offerFrist(E e)、offerLast(E e)、pollFrist(E e)、pollLast(E e)的话,性能是非常好的,因为可以避免搜索过程。

      类似的,LinkedList为了实现remove(int index)方法,也必须先通过node(int index)方法找到index索引处的节点,然后修改它前一个节点的next引用,以及后一个节点的previous引用,下面是该方法的源代码。

        public E remove(int index) {
            checkElementIndex(index);
            return unlink(node(index));
        }
    
        E unlink(Node<E> x) {
            // assert x != null;
            final E element = x.item;
            final Node<E> next = x.next;
            final Node<E> prev = x.prev;
    
            if (prev == null) {
                first = next;
            } else {
                prev.next = next;
                x.prev = null;
            }
    
            if (next == null) {
                last = prev;
            } else {
                next.prev = prev;
                x.next = null;
            }
    
            x.item = null;
            size--;
            modCount++;
            return element;
        }

       综上所述,ArrayList、LinkedList有各自适用的场景,大部分情况下,ArrayList的性能总是优于Linkedlist,因此绝大部分都应该考虑使用ArrayList集合。但如果程序荆楚需要添加、删除元素,尤其是经常需要add(E e)方法向集合中添加元素,则应该考虑使用LinkedList集合了。

  • 相关阅读:
    Python 脚本如何执行另一个脚本
    Yarn集群的搭建、Yarn的架构和WordCount程序在集群提交方式
    Mapreduce概述和WordCount程序
    HDFS及其各组件的机制
    HDFS常用API(2)
    HDFS常用API(1)
    HDFS分布式文件系统的常用命令行操作
    大数据及Hadoop的概述
    谷歌浏览器加载速度慢的解决办法!!!
    Idea中lombok不生效原因
  • 原文地址:https://www.cnblogs.com/conswin/p/6971346.html
Copyright © 2011-2022 走看看