zoukankan      html  css  js  c++  java
  • List接口实现类-ArrayList、Vector、LinkedList集合深入学习以及源代码解析

    学习List接口实现类 ArrayList  Vector  LinkedList

    List接口的实现类中最经常使用最重要的就是这三个:ArrayList、Vector、LinkedList。

    JDK中这三个类的定义:

    1、ArrayList<E>:

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
        private static final long serialVersionUID = 8683452581122892189L;

        /**
         * The array buffer into which the elements of the ArrayList are stored.
         * The capacity of the ArrayList is the length of this array buffer.
         */
        private transient Object[] elementData;

        /**
         * The size of the ArrayList (the number of elements it contains).
         *
         * @serial
         */
        private int size;


    2、 Vector<E>:
    public class Vector<E>extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
        /**
         * The array buffer into which the components of the vector are
         * stored. The capacity of the vector is the length of this array buffer,
         * and is at least large enough to contain all the vector's elements.
         *
         * <p>Any array elements following the last element in the Vector are null.
         *
         * @serial
         */
        protected Object[] elementData;

    3、LinkedList<E>:

    public class LinkedList<E>extends AbstractSequentialList<E>
        implements List<E>, Deque<E>, Cloneable, java.io.Serializable
    {   // 在序列化这个对象的时候这个变量不会被这样序列化
        transient int size = 0;

        /**
         * Pointer to first node.
         * Invariant: (first == null && last == null) ||
         *            (first.prev == null && first.item != null)
         */
        transient Node<E> first;

        /**
         * Pointer to last node.
         * Invariant: (first == null && last == null) ||
         *            (last.next == null && last.item != null)
         */
        transient Node<E> last;

    4、从这三个类定义就能够看出一些信息:

    (1)、三个都直接实现了AbstractList这个抽象类

    (2)、ArrayList和Vector都实现了RandomAccess接口。而LinkedList没有。这是什么意思呢?             

     在JDK 中,RandomAccess接口是一个空接口,所以它没有实际意义。就是一个标记,

     标记这个类支持高速随机訪问,所以,arrayList和 vector是支持随机訪问的,可是LinkedList不支持持         

    (3)、serializbale接口表明他们都支持序列化。



    5、以下具体说说List的这三个实现:

      (1)、 查看实现源代码会发现这三个里面,ArrayList和Vector使用了数组的实现,相当于封装了对数组的操作。这也正是他们可以支持高速随机訪问的原因,多说一句,JDK中全部基于数组实现的数据结构都可以支持高速随机訪问。

       ArrayList和Vector的实现上差点儿都使用了同样的算法,他们的主要差别就是ArrayList没有对不论什么一个方法做同步处理,所以不是线程安全的;而Vector中大部分方法都做了线程同步所以是线程安全的。

      (2)、LinkedList使用的是双向循环链表的数据结构。因为是基于链表的。所以无法法实现随机訪问的,仅仅能顺序訪问,这也正是它没有实现RandomAccess接口的原因。

      (3)、因为ArrayList、Vector和LinkedList所採用的数据结构不同。注定他们适用的是全然不同的场景。

    通过阅读这几个类的源代码,我们能够看到他们实现的不同。

    ArrayList和Vector基本一样,我们就用ArrayList和LinkedList做对照。

    在末尾添加一个元素

    6、ArrayList中的add方法实现例如以下:

          /**
         * Appends the specified element to the end of this list.
         *将元素加入到list的最后
         * @param e element to be appended to this list
         * @return <tt>true</tt> (as specified by {@link Collection#add})
         */
        public boolean add(E e) {
            // 推断容量

            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }


    这种方法做两件事情,首先确保数组空间足够大。然后在数组末尾添加元素而且通过后++使得完毕size+1

    从这个代码能够看出,假设数组空间足够大,那么仅仅是数组的add操作就是O(1)的性能,很高效。

    7、在看看ensureCapacityInternal这种方法的实现:

     private void ensureCapacityInternal(int minCapacity) {
            modCount++;
            // overflow-conscious code。假设空间不够则扩容也就是又一次创建一个Object[]对象
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }

    8、grow(int minCapacity)方法创建数组并将原来的数据复制到新数组中

      /**
         * Increases the capacity to ensure that it can hold at least the
         * number of elements specified by the minimum capacity argument.
         *
         * @param minCapacity the desired minimum capacity
         */
        private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            // 使用移位运算,提高效率

           int newCapacity = oldCapacity + (oldCapacity >> 1);
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
        }

    能够看出。假设数组空间不够,那么这种方法就会做数组扩容和数组复制操作,看上面,JDK利用移位运算符进行扩容计算,>>1右移一位表示除2,所以newCapacity就是扩容为原来的1.5倍。

    9、这里的代码都是JDK1.7中的实现,JDK1.7对1.6的非常多代码做了优化。比方上面这段扩容代码,在JDK1.6中上面的是直接除2,显然。移位运算要更高效。

    10、在看看LinkedList中的add方法:

    (1)、add(E e):

    /**
         * Appends the specified element to the end of this list.
         *
         * <p>This method is equivalent to {@link #addLast}.
         *
         * @param e element to be appended to this list
         * @return {@code true} (as specified by {@link Collection#add})
         */
        public boolean add(E e) {
            linkLast(e);
            return true;
        }

    (2)、linkLast(E e)

        /**
         * Links e as last element.
         */
        void linkLast(E e) {
            final Node<E> l = last;
            final Node<E> newNode = new Node<>(l, e, null);
            last = newNode;
            if (l == null)
                first = newNode;
            else
                l.next = newNode;
            size++;
            modCount++;
        }


    (3)、内部类:
      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;
            }
        }

    从这段add代码能够看出。LinkedList因为使用了链表。所以不须要进行扩容,直接把元素加到链表最后,把新元素的前驱指向之前的last元素。并把last元素指向新元素就能够了。

    这也是一个O(1)的性能。

    在任何位置插入元素

    11、ArrayList中的实现例如以下:

    (1)、 add(int index, E element)

    public void add(int index, E element) {
            rangeCheckForAdd(index);
             // 推断容量
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            System.arraycopy(elementData, index, elementData, index + 1,
                             size - index);
            elementData[index] = element;
            size++;
        }

    (2)、ensureCapacityInternal(int minCapacity)

    private void ensureCapacityInternal(int minCapacity) {
            modCount++;
            // overflow-conscious code,假设数组长度不够则进行扩容
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }

    (3)、grow(int minCapacity)

    /**
         * Increases the capacity to ensure that it can hold at least the
         * number of elements specified by the minimum capacity argument.
         *
         * @param minCapacity the desired minimum capacity
         */
        private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
        }


    这段代码,首先先检查数组容量,容量不够先扩容。然后把index之后的数组往后挪一个。最后在index位置放上新元素。因为数组是一块连续内存空间,所以在任何位置插入。都会导致这个其后数组后挪一位的情况。须要做一次数组复制操作,非常明显,假设有大量的随机插入,那么这个数组复制操作开销会非常大。并且插入的越靠前,数组复制开销越大。

    12、LinkedList中的实现:

    (1)、add(int index, E element)

     /**
         * Inserts the specified element at the specified position in this list.
         * Shifts the element currently at that position (if any) and any
         * subsequent elements to the right (adds one to their indices).
         *
         * @param index index at which the specified element is to be inserted
         * @param element element to be inserted
         * @throws IndexOutOfBoundsException {@inheritDoc}
         */
        public void add(int index, E element) {
            checkPositionIndex(index);

            if (index == size)
                linkLast(element);
            else
                linkBefore(element, node(index));
        }


    (2)、linkBefore(E e, Node<E> succ)
     /**
         * Inserts element e before non-null Node 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++;
        }


    (3)、 Node<E> node(int index)

    /**
         * Returns the (non-null) Node at the specified element index.
         */
        Node<E> node(int index) {
            // assert isElementIndex(index);

            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;
            }
        }

    这段代码。取到原先index处节点的前驱。变成新节点的前驱,同一时候把原先index变成新节点的后驱,这样就完毕了新节点的插入。

    这个就是链表的优势。不存在数据复制操作,性能和在最后插入是一样的。


    小结:

        从上面的源代码剖析能够看出这三种List实现的一些典型适用场景,假设常常对数组做随机插入操作,特别是插入的比較靠前,那么LinkedList的性能优势就很明显。而假设都仅仅是末尾插入,则ArrayList更占领优势。假设须要线程安全。则使用Vector或者创建线程安全的ArrayList。

    在使用基于数组实现的ArrayList 和Vector 时我们要指定初始容量。由于我们在源代码中也看到了,在加入时首先要进行容量的推断,假设容量不够则要创建新数组。还要将原来数组中的数据拷贝到新数组中。这个过程会减低效率而且会浪费资源。

     


  • 相关阅读:
    封装、权限修饰符、包、构造器
    从Discuz!NT v2.0扣出来的生成静态页面的方法
    C#中struct与class的区别
    Asp.Net中虚拟文件系统的使用
    C#生成中文验证码
    C#导入Excel表
    IIS5、IIS6、IIS7的ASP.net 请求处理过程比较
    如何保证Session值不丢失
    C#抽象类与接口的区别
    C#实现DES加密解密
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/5157768.html
Copyright © 2011-2022 走看看