• Java 集合:(六) List实现类:LinkedList


    一、LinkedList 概述

      1、对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高。

      2、LinkedList是一个实现了List接口Deque接口双端链表。 

        

      3、LinkedList底层的链表结构使它支持高效的插入和删除操作,另外它实现了Deque接口,使得LinkedList类也具有 List 的操作以及双端队列和栈的性质。

      4、LinkedList不是线程安全的,如果想使LinkedList变成线程安全的,可以调用静态类Collections类中的synchronizedList方法:

    List list=Collections.synchronizedList(new LinkedList(...));
    

      

      5、LinkedList 的继承结构如下:

        

    二、实现的接口

        源码:

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

        (1)实现了 List 接口,具有List的操作方法;

        (2)实现了 Deque 接口,具有双端队列和栈的特性;

        (3)实现了 Cloneable 接口,支持克隆;

        (4)实现了 Serializable 接口,支持序列化;

    三、内部结构

      1、双向链表

        ArrayList是通过数组实现存储,而LinkedList则是通过链表来存储数据,而且他实现的是一个双向链表,简单的说一下什么是双向链表。

        双向链表是数据结构的一种形式,他的每个节点维护两个指针,prev指向上一个节点,next指向下一个节点。

        这种结构有什么特点呢?他可以实现双向遍历,这使得在链表中的数据读取变得非常灵活自由。

        同时,LinkedList中维护了两个指针,一个指向头部,一个指向尾部。维护这两个指针后,可以使得元素从头部插入,也可以使元素从尾部插入。

        基于方式,用户很容易就能实现FIFO(队列),LIFO(栈)等效果。那么下面我们来看一下源码中的具体实现。

      2、内部结构分析

          如图所示:

             

        LinkedList:双向链表,内部没有声明数组,而是定义了Node类型的first和last,用于记录首末元素。同时,定义内部类Node,作为LinkedList中保存数据的基
    本结构。
        Node除了保存数据,还定义了两个变量:
          ① prev变量记录前一个元素的位置 
          ② next变量记录下一个元素的位置

        看完了图之后,我们再看LinkedList类中的一个内部私有类Node就很好理解了:

     1     private static class Node<E> {
     2         E item;         //本节点的值
     3         Node<E> next;   //后继节点
     4         Node<E> prev;   //前驱节点
     5 
     6         Node(Node<E> prev, E element, Node<E> next) {
     7             this.item = element;
     8             this.next = next;
     9             this.prev = prev;
    10         }
    11     }

        这个类就代表双端链表的节点Node。这个类有三个属性,分别是前驱节点,本节点的值,后继结点。

    四、成员变量

        在 LinkedList 类中,还有几个成员变量如下:

     1 // list 的长度
     2 transient int size = 0;
     3 
     4 // 链表头结点
     5 transient Node<E> first;
     6 
     7 // 链表尾结点
     8 transient Node<E> last;
     9 
    10 //序列化标记
    11 private static final long serialVersionUID = 876323262645176354L;

    五、构造器

        LinkedList 有两个构造器,如下:

        (1)无参构造:

    1     public LinkedList() {
    2     }

        (2)用已有的集合创建链表的构造方法:

    1     public LinkedList(Collection<? extends E> c) {
    2         this();
    3         addAll(c);
    4     }

        注意:由于链表的容量可以一直增加,因此没有指定容量的构造器。

        第一个为无参构造器。
        第二个为使用指定集合的集合构造,并调用 addAll() 方法,继续跟进该方法,代码如下:
     1 public boolean addAll(Collection<? extends E> c) {
     2     return addAll(size, c);
     3 }
     4     
     5 public boolean addAll(int index, Collection<? extends E> c) {
     6     //1:检查index范围是否在size之内
     7     checkPositionIndex(index);
     8     
     9     //2:toArray()方法把集合的数据存到对象数组中
    10     Object[] a = c.toArray();
    11     int numNew = a.length;
    12     if (numNew == 0)
    13         return false;
    14         
    15         
    16     //3:获取当前链表的前驱和后继结点,得到插入位置的前驱节点和后继节点
    17     Node<E> pred, succ;
    18     //如果插入位置为尾部,前驱节点为last,后继节点为null
    19     if (index == size) { 
    20         succ = null;
    21         pred = last;
    22     } 
    23     //若非尾结点,获取指定位置的结点,调用node()方法得到后继节点,再得到前驱节点,
    24     else { 
    25         succ = node(index);  //获取当前节点
    26         pred = succ.prev;    //获取当前节点前驱节点
    27     }
    28     
    29     // 4:循环将数组中的元素插入到链表
    30     for (Object o : a) {
    31         @SuppressWarnings("unchecked") E e = (E) o;
    32         //创建新节点
    33         Node<E> newNode = new Node<>(pred, e, null);
    34         //如果插入位置在链表头部
    35         if (pred == null)
    36             first = newNode;
    37         else
    38             pred.next = newNode;
    39         pred = newNode;
    40     }
    41     
    42     //如果插入位置在尾部,重置last节点
    43     // 若插入到末尾,则数组中的最后一个元素就是尾结点
    44     if (succ == null) {
    45         last = pred;
    46     } 
    47     
    48     //否则,将插入的链表与先前链表连接起来
    49     else {
    50         // 若插入到指定位置,将数组中最后一个元素与下一个位置关联起来
    51         pred.next = succ;
    52         succ.prev = pred;
    53     }
    54     size += numNew;
    55     modCount++;
    56     return true;
    57 }
    58 
    59 private void checkPositionIndex(int index) {
    60     if (!isPositionIndex(index))
    61         throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    62 }
    63 
    64 private boolean isPositionIndex(int index) {
    65         return index >= 0 && index <= size;
    66 }

      上面可以看出addAll方法通常包括下面四个步骤:

      1. 检查index范围是否在size之内;

      2. toArray()方法把集合的数据存到对象数组中;

      3. 得到插入位置的前驱和后继节点;

      4. 遍历数据,将数据插入到指定位置,如果没有在尾部,把原来数据链起来;

      其中 node(index) 方法为获取指定位置的结点,代码如下:

     1     Node<E> node(int index) {
     2         // assert isElementIndex(index);
     3         // 判断下标在哪里,若下标在前一半,则从前往后遍历;否则从后往前遍历
     4         if (index < (size >> 1)) {
     5             Node<E> x = first;
     6             for (int i = 0; i < index; i++)
     7                 x = x.next;
     8             return x;
     9         } else {
    10             Node<E> x = last;
    11             for (int i = size - 1; i > index; i--)
    12                 x = x.prev;
    13             return x;
    14         }
    15     }
        该方法通过遍历链表获取指定的元素。
        值得注意的是,该方法并非直接从头到尾遍历整个链表,而是先判断下标的位置,若在前一半则从前往后遍历;否则就从后往前遍历。这样能减少遍历结点的个数。
     
        因为链表的内存空间是非连续的,所以不支持随机访问(下标访问)。所以,查询某个结点是通过遍历整个链表来实现的。
     

    六、常用方法

      1、新增结点方法【尾插法】:add(),addLast(),offer(),offerLast()

        源码分析:

     1     public boolean add(E e) {
     2         linkLast(e);
     3         return true;
     4     }
     5     public void addLast(E e) {
     6         linkLast(e);
     7     }
     8     public boolean offer(E e) {
     9         return add(e);
    10     }
    11     public boolean offerLast(E e) {
    12         addLast(e);
    13         return true;
    14     }

      可以看到他们都是调用了同一个方法 linkLast(e) 实现的,如下:

     1     /**
     2      * Links e as last element.
     3      */
     4     void linkLast(E e) {
     5         final Node<E> l = last;
     6         // 创建一个节点,将prev指针指向链表的尾节点。
     7         final Node<E> newNode = new Node<>(l, e, null); 
     8 
     9         // 将last指针指向新创建的这个节点。        
    10         last = newNode;
    11         
    12         if (l == null)
    13             // 如果当前链表为空,那么将头指针也指向这个节点。
    14             first = newNode;
    15 
    16         else
    17             // 若链表不为空,将新结点插入到链表尾部
    18             // 将链表的尾节点的next指针指向新建的节点,这样就完整的实现了在链表尾部添加一个元素的功能。
    19             l.next = newNode;
    20         size++;
    21         modCount++;
    22     }

        该操作就是将指定的结点添加到链表末尾。

      2、新增节点【头插法】:addFirst(),offerFirst()

        源码:

    1     public void addFirst(E e) {
    2         linkFirst(e);
    3     }
    4     public boolean offerFirst(E e) {
    5         addFirst(e);
    6         return true;
    7     }

        可以看到他们都是调用了同一个方法 linkFirst(e) 实现的,如下:

     1     /**
     2      * Links e as first element.
     3      */
     4     private void linkFirst(E e) {
     5         final Node<E> f = first;
     6         // 创建一个新元素,将元素的next指针指向当前的头结点
     7         final Node<E> newNode = new Node<>(null, e, f);
     8          // 将头指针指向这个节点。
     9         first = newNode;
    10         if (f == null)
    11             // 如果当前节点为空,则把尾指针指向这个节点。
    12             last = newNode;
    13         else
    14             // 将当前头结点的prev指针指向此结点。
    15             f.prev = newNode;
    16         size++;
    17         modCount++;
    18     }

        这段代码就是实现将元素添加的链表头部。

      3、新增节点【指定位置插入】:add(int index, E element)

        源码:

    1     public void add(int index, E element) {
    2         checkPositionIndex(index);
    3 
    4         if (index == size)
    5             linkLast(element);
    6         else
    7             linkBefore(element, node(index));
    8     }

        在这里分了两种情况:

        ① 如果刚好到尾部,直接在尾部插入;

        ② 如果没有在尾部,在非null节点之前插入元素e。

        源码:

     1     void linkLast(E e) {
     2         final Node<E> l = last;
     3         final Node<E> newNode = new Node<>(l, e, null);
     4         last = newNode;
     5         if (l == null)
     6             first = newNode;
     7         else
     8             l.next = newNode;
     9         size++;
    10         modCount++;
    11     }
    12     
    13     void linkBefore(E e, Node<E> succ) {
    14         // assert succ != null;
    15         final Node<E> pred = succ.prev;
    16         final Node<E> newNode = new Node<>(pred, e, succ);
    17         succ.prev = newNode;
    18         if (pred == null)
    19             first = newNode;
    20         else
    21             pred.next = newNode;
    22         size++;
    23         modCount++;
    24     }

      4、设置值:set(int index, E element)

        源码:

     1     public E set(int index, E element) {
     2         //索引检查
     3         checkElementIndex(index);
     4         
     5         //获取该索引的元素
     6         Node<E> x = node(index);
     7         E oldVal = x.item;
     8         x.item = element;
     9         return oldVal;
    10     }

      5、查找值:get(int index)

        源码:

     1     public E get(int index) {
     2         checkElementIndex(index);
     3         return node(index).item;
     4     }
     5     private void checkElementIndex(int index) {
     6         if (!isElementIndex(index))
     7             throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
     8     }
     9     private boolean isElementIndex(int index) {
    10         return index >= 0 && index < size;
    11     }

        可以看到,这里还是调用了上面的 node() 方法进行查找的。

      6、获取头节点:

        源码:

     1     public E getFirst() {
     2         final Node<E> f = first;
     3         if (f == null)
     4             throw new NoSuchElementException();
     5         return f.item;
     6     }
     7     public E element() {
     8         return getFirst();
     9     }
    10     public E peek() {
    11         final Node<E> f = first;
    12         return (f == null) ? null : f.item;
    13     }
    14     public E peekFirst() {
    15         final Node<E> f = first;
    16         return (f == null) ? null : f.item;
    17     }

        区别:

        getFirst(),element(),peek(),peekFirst() 这四个获取头结点方法的区别在于对链表为空时的处理,是抛出异常还是返回null,

        其中getFirst()element() 方法将会在链表为空时,抛出异常;element()方法的内部就是使用getFirst()实现的。它们会在链表为空时,抛出NoSuchElementException;

        而 peek() 和 peekFirst() 方法在链表为空时会返回空;

      7、获取尾结点

        源码:

     1     public E getLast() {
     2         final Node<E> l = last;
     3         if (l == null)
     4             throw new NoSuchElementException();
     5         return l.item;
     6     }
     7     public E peekLast() {
     8         final Node<E> l = last;
     9         return (l == null) ? null : l.item;
    10     }

        两者区别: getLast() 方法在链表为空时,会抛出NoSuchElementException,而peekLast() 则不会,只是会返回 null

      8、根据对象得到索引的方法

        int indexOf(Object o): 从头遍历找

     1     public int indexOf(Object o) {
     2         int index = 0;
     3         if (o == null) {
     4             //从头遍历
     5             for (Node<E> x = first; x != null; x = x.next) {
     6                 if (x.item == null)
     7                     return index;
     8                 index++;
     9             }
    10         } else {
    11             //从头遍历
    12             for (Node<E> x = first; x != null; x = x.next) {
    13                 if (o.equals(x.item))
    14                     return index;
    15                 index++;
    16             }
    17         }
    18         return -1;
    19     }

        int lastIndexOf(Object o): 从尾遍历找

     1     public int lastIndexOf(Object o) {
     2         int index = size;
     3         if (o == null) {
     4             //从尾遍历
     5             for (Node<E> x = last; x != null; x = x.prev) {
     6                 index--;
     7                 if (x.item == null)
     8                     return index;
     9             }
    10         } else {
    11             //从尾遍历
    12             for (Node<E> x = last; x != null; x = x.prev) {
    13                 index--;
    14                 if (o.equals(x.item))
    15                     return index;
    16             }
    17         }
    18         return -1;
    19     }

      9、检查链表是否包含某对象的方法:contains()

        源码:

    1     public boolean contains(Object o) {
    2         return indexOf(o) != -1;
    3     }

      10、删除头节点方法:remove() ,removeFirst(),pop(),poll(),pollFirst()

        源码:

     1     public E remove() {
     2         return removeFirst();
     3     }
     4     
     5     public E pop() {
     6         return removeFirst();
     7     }
     8     
     9     public E removeFirst() {
    10         final Node<E> f = first;
    11         if (f == null)
    12             throw new NoSuchElementException();
    13         return unlinkFirst(f);
    14     }
    15     
    16     public E poll() {
    17         final Node<E> f = first;
    18         return (f == null) ? null : unlinkFirst(f);
    19     }
    20     public E pollFirst() {
    21         final Node<E> f = first;
    22         return (f == null) ? null : unlinkFirst(f);
    23     }

        本质上都是调用了 unlinkFirst()方法

        源码:

     1     private E unlinkFirst(Node<E> f) {
     2         // assert f == first && f != null;
     3         final E element = f.item;
     4         final Node<E> next = f.next;
     5         f.item = null;
     6         f.next = null; // help GC
     7         first = next;
     8         if (next == null)
     9             last = null;
    10         else
    11             next.prev = null;
    12         size--;
    13         modCount++;
    14         return element;
    15     }

      11、删除尾节点方法:removeLast(),pollLast()

        源码:

     1     public E removeLast() {
     2         final Node<E> l = last;
     3         if (l == null)
     4             throw new NoSuchElementException();
     5         return unlinkLast(l);
     6     }
     7     
     8     public E pollLast() {
     9         final Node<E> l = last;
    10         return (l == null) ? null : unlinkLast(l);
    11     }

        区别: removeLast()在链表为空时将抛出NoSuchElementException,而pollLast()方法返回null。

        本质上都是调用了 unlinkLast()方法。

        源码:

     1   private E unlinkLast(Node<E> l) {
     2         // assert l == last && l != null;
     3         final E element = l.item;
     4         final Node<E> prev = l.prev;
     5         l.item = null;
     6         l.prev = null; // help GC
     7         last = prev;
     8         if (prev == null)
     9             first = null;
    10         else
    11             prev.next = null;
    12         size--;
    13         modCount++;
    14         return element;
    15     }

      12、删除指定元素:remove(Object o) & 删除指定位置的元素:remove(int index)

        源码:

     1     public boolean remove(Object o) {
     2         if (o == null) {
     3             for (Node<E> x = first; x != null; x = x.next) {
     4                 if (x.item == null) {
     5                     unlink(x);
     6                     return true;
     7                 }
     8             }
     9         } else {
    10             for (Node<E> x = first; x != null; x = x.next) {
    11                 if (o.equals(x.item)) {
    12                     unlink(x);
    13                     return true;
    14                 }
    15             }
    16         }
    17         return false;
    18     }
    19     
    20     public E remove(int index) {
    21         checkElementIndex(index);
    22         return unlink(node(index));
    23     }

        当删除指定对象时,只需调用remove(Object o)即可,不过该方法一次只会删除一个匹配的对象,如果删除了匹配对象,返回true,否则false。

        本质上还是调用了 unlink(Node<E> x) 方法:

     1   E unlink(Node<E> x) {
     2         // assert x != null;
     3         final E element = x.item;
     4         final Node<E> next = x.next;//得到后继节点
     5         final Node<E> prev = x.prev;//得到前驱节点
     6 
     7         //删除前驱指针
     8         if (prev == null) {
     9             first = next;//如果删除的节点是头节点,令头节点指向该节点的后继节点
    10         } else {
    11             prev.next = next;//将前驱节点的后继节点指向后继节点
    12             x.prev = null;
    13         }
    14 
    15         //删除后继指针
    16         if (next == null) {
    17             last = prev;//如果删除的节点是尾节点,令尾节点指向该节点的前驱节点
    18         } else {
    19             next.prev = prev;
    20             x.next = null;
    21         }
    22 
    23         x.item = null;
    24         size--;
    25         modCount++;
    26         return element;
    27     }

      13、序列化方法:writeObject(java.io.ObjectOutputStream s)

        源码:

     1 private void writeObject(java.io.ObjectOutputStream s)
     2     throws java.io.IOException {
     3     // Write out any hidden serialization magic
     4     s.defaultWriteObject();
     5 
     6     // Write out size
     7     s.writeInt(size);
     8 
     9     // Write out all elements in the proper order.
    10     for (Node<E> x = first; x != null; x = x.next)
    11         s.writeObject(x.item);
    12 }

      14、反序列化方法:readObject(java.io.ObjectInputStream s)

        源码:

     1 private void readObject(java.io.ObjectInputStream s)
     2     throws java.io.IOException, ClassNotFoundException {
     3     // Read in any hidden serialization magic
     4     s.defaultReadObject();
     5 
     6     // Read in size
     7     int size = s.readInt();
     8 
     9     // Read in all elements in the proper order.
    10     for (int i = 0; i < size; i++)
    11         linkLast((E)s.readObject());
    12 }

    七、作为其他数据结构

      1、FIFO(队列)实现原理

        队列的原理就是每次都从链表尾部添加元素,从链表头部获取元素,就像生活中的排队叫号,总是有个先来后到。

        源码:

     1 // 队列尾部添加一个元素,建议使用这个,约定俗成吧。
     2 publicboolean offer(E e){
     3     return add(e);
     4 }
     5 
     6 // 队列尾部添加一个元素
     7 publicboolean offerLast(E e){
     8     addLast(e);
     9     return    true;
    10 }
    11 
    12 // offer和offerLast底层调用的都是linkLast这个方法,顾名思义就是将元素添加到链表尾部。
    13 void linkLast(E e){
    14     finalNode<E> l =last;
    15     
    16     // 创建一个节点,将prev指针指向链表的尾节点。
    17     finalNode<E> newNode =newNode<>(l, e,null);
    18     
    19     // 将last指针指向新创建的这个节点。
    20     last= newNode;
    21     
    22     if(l ==null)
    23         // 如果当前链表为空,那么将头指针也指向这个节点。
    24         first = newNode;
    25     else
    26         // 将链表的尾节点的next指针指向新建的节点,这样就完整的实现了在链表尾部添加一个元素的功能。
    27         l.next= newNode;
    28         
    29     size++;
    30     modCount++;
    31 }
    32 
    33 // 在链表头部删除一个元素,建议用这个
    34 public E poll(){
    35     final    Node<E> f = first;
    36     return(f ==null)?null: unlinkFirst(f);
    37 }
    38 // 在链表头部删除一个元素
    39 public E pollFirst(){
    40     final    Node<E> f = first;
    41     return(f ==null)?null: unlinkFirst(f);
    42 }
    43 
    44 // poll和pollFirst底层调用的就是这个方法,将链表的头元素删除。
    45 private E unlinkFirst(Node<E> f){
    46     // assert f == first && f != null;
    47     final E element = f.item;
    48     final    Node<E>next= f.next;
    49     f.item =null;
    50     f.next=null;// help GC
    51     first =next;
    52     if(next==null)
    53         last=null;
    54     else
    55         next.prev =null;
    56     size--;
    57     modCount++;
    58     return element;
    59 }
    60 
    61 // 获取头元素,但是不会删除他。
    62 public E peek(){
    63     final    Node<E> f = first;
    64     return(f ==null)?null: f.item;
    65 }

        更准确来说,链表是一个双端链表的结构,可以在头尾都进行操作节点。

      2、LIFO(栈)实现原理:

        栈的原理是每次从头部添加元素,也从头部获取元素,那么后进入的元素反而最先出来。就像我们平时叠盘子,洗好了就一个一个往上放,然后要用了就从上往下一个一个拿。

        源码:

     1 // 在链表的头部添加一个元素
     2 publicvoid push(E e){
     3     addFirst(e);
     4 }
     5 
     6 // addFirst调用的就是linkFirst,这段代码就是实现将元素添加的链表头部。
     7 private void linkFirst(E e){
     8     final    Node<E> f = first;
     9     // 创建一个新元素,将元素的next指针指向当前的头结点
    10     final    Node<E> newNode =newNode<>(null, e, f);
    11     // 将头指针指向这个节点。
    12     first = newNode;
    13     if(f ==null)
    14         // 如果当前节点为空,则把尾指针指向这个节点。
    15         last= newNode;
    16     else
    17         // 将当前头结点的prev指针指向此结点。
    18         f.prev = newNode;
    19     size++;
    20     modCount++;
    21 }
    22 
    23 // 弹出顶部结点。
    24 public E pop(){
    25     return removeFirst();
    26 }
    27 
    28 // removeFirst调用的就是unlinkFirst,unlinkFirst实现将链表顶部元素删除
    29 private E unlinkFirst(Node<E> f){
    30     // assert f == first && f != null;
    31     final E element = f.item;
    32     final Node<E>next= f.next;
    33     f.item =null;
    34     f.next=null;// help GC
    35     first =next;
    36     if(next==null)
    37         last=null;
    38     else
    39         next.prev =null;
    40     size--;
    41     modCount++;
    42     return element;
    43 }
    44 
    45 // 获取顶部结点,但是不删除
    46 public E peek(){
    47     final    Node<E> f = first;
    48     return(f ==null)?null: f.item;
    49 }

    八、迭代器相关

      LinkedList的迭代器实现有两个,一个是实现了Iterator接口的DescendingIterator,另一个则是实现了ListIterator接口的ListItr。

      1、ListItr

        源码:

      1 public ListIterator<E> listIterator(int index) {
      2     checkPositionIndex(index);
      3     return new ListItr(index);
      4 }
      5 
      6 private class ListItr implements ListIterator<E> {
      7     private Node<E> lastReturned;
      8     private Node<E> next;
      9     private int nextIndex;
     10     private int expectedModCount = modCount;
     11 
     12     // 实例化的时候,将next指针指向指定位置的元素
     13     ListItr(int index) {
     14         // assert isPositionIndex(index);
     15         next = (index == size) ? null : node(index);
     16         nextIndex = index;
     17     }
     18 
     19     public boolean hasNext() {
     20         return nextIndex < size;
     21     }
     22 
     23     // 向后遍历
     24     public E next() {
     25         checkForComodification();
     26         if (!hasNext())
     27             throw new NoSuchElementException();
     28 
     29         lastReturned = next;
     30         next = next.next;
     31         nextIndex++;
     32         return lastReturned.item;
     33     }
     34 
     35     public boolean hasPrevious() {
     36         return nextIndex > 0;
     37     }
     38 
     39     // 向前遍历
     40     public E previous() {
     41         checkForComodification();
     42         if (!hasPrevious())
     43             throw new NoSuchElementException();
     44 
     45         lastReturned = next = (next == null) ? last : next.prev;
     46         nextIndex--;
     47         return lastReturned.item;
     48     }
     49 
     50     public int nextIndex() {
     51         return nextIndex;
     52     }
     53 
     54     public int previousIndex() {
     55         return nextIndex - 1;
     56     }
     57 
     58     public void remove() {
     59         checkForComodification();
     60         if (lastReturned == null)
     61             throw new IllegalStateException();
     62 
     63         Node<E> lastNext = lastReturned.next;
     64         unlink(lastReturned);
     65         if (next == lastReturned)
     66             next = lastNext;
     67         else
     68             nextIndex--;
     69         lastReturned = null;
     70         expectedModCount++;
     71     }
     72 
     73     public void set(E e) {
     74         if (lastReturned == null)
     75             throw new IllegalStateException();
     76         checkForComodification();
     77         lastReturned.item = e;
     78     }
     79 
     80     public void add(E e) {
     81         checkForComodification();
     82         lastReturned = null;
     83         if (next == null)
     84             linkLast(e);
     85         else
     86             linkBefore(e, next);
     87         nextIndex++;
     88         expectedModCount++;
     89     }
     90 
     91     public void forEachRemaining(Consumer<? super E> action) {
     92         Objects.requireNonNull(action);
     93         while (modCount == expectedModCount && nextIndex < size) {
     94             action.accept(next.item);
     95             lastReturned = next;
     96             next = next.next;
     97             nextIndex++;
     98         }
     99         checkForComodification();
    100     }
    101 
    102     final void checkForComodification() {
    103         if (modCount != expectedModCount)
    104             throw new ConcurrentModificationException();
    105     }
    106 }

      2、DescendingIterator

        DescendingIterator迭代器实现的是对链表从尾部向头部遍历的功能,他复用里ListItr中的previous方法,将当前位置指向链表尾部,然后逐个向前遍历。

        源码:

     1     private class DescendingIterator implements Iterator<E> {
     2         private final ListItr itr = new ListItr(size());
     3         public boolean hasNext() {
     4             return itr.hasPrevious();
     5         }
     6         public E next() {
     7             return itr.previous();
     8         }
     9         public void remove() {
    10             itr.remove();
    11         }
    12     }

    九、不同版本的 LinkedList

      在LinkedList 中 JDK1.6 之前为双向循环链表,JDK1.7 取消了循环,采用双向链表

      1、双向链表

        双向链表属于链表的一种,也叫双链表双向即是说它的链接方向是双向的,它由若干个节点组成,每个节点都包含下一个节点和上一个节点的指针,所以从双向链表的任意节点开始,都能很方便访问他的前驱结点和后继节点。

        

      2、双向链表特点

      • 创建双链表时无需指定链表的长度。
      • 比起单链表,双链表需要多一个指针用于指向前驱节点,所以需要存储空间比单链表多一点。
      • 双链表的插入和删除需要同时维护 next 和 prev 两个指针。
      • 双链表中的元素访问需要通过顺序访问,即要通过遍历的方式来寻找元素。

      3、双向循环链表

        前面的双向链表的 head 节点和链尾没有连接关系,所以如果要访问最后一个节点的话需要从头开始遍历,直到最后一个节点。在双向链表基础上改进一下,把 header 节点的 prev 指针指向最后一个节点,而最后一个节点的 next 指针指向 header 节点,于是便构成双向循环链表。
        

           

           更多链表操作:https://juejin.cn/post/6844903648154271757#heading-0

       4、JDK6 

        在JDK 1.7之前(此处使用JDK1.6来举例),LinkedList是通过headerEntry实现的一个循环链表的。先初始化一个空的Entry,用来做header,然后首尾相连,形成一个循环链表:

    1 privatetransient Entry<E>header =new Entry<E>(null,null,null);
    2 
    3 public LinkedList() {header.next =header.previous =header; }

        

        在LinkedList中提供了两个基本属性size、header。

    1 private transient Entry<E> header = new Entry<E>(null, null, null);
    2 private transient int size = 0;
    • 其中size表示的LinkedList的大小,header表示链表的表头,Entry为节点对象。
     1 private static class Entry<E> {
     2         E element;        //元素节点
     3         Entry<E> next;    //下一个元素
     4         Entry<E> previous;  //上一个元素
     5  
     6         Entry(E element, Entry<E> next, Entry<E> previous) {
     7             this.element = element;
     8             this.next = next;
     9             this.previous = previous;
    10         }
    11 }
    • 上面为Entry对象的源代码,Entry为LinkedList的内部类,它定义了存储的元素。该元素的前一个元素、后一个元素,这是典型的双向链表定义方式。

      每次添加/删除元素都是默认在链尾操作。对应此处,就是在header前面操作,因为遍历是next方向的,所以在header前面操作,就相当于在链表尾操作。

      如下面的插入操作addBefore以及图示,如果插入obj_3,只需要修改header.previous和obj_2.next指向obj_3即可。

      

     1 private Entry<E> addBefore(E e, Entry<E> entry) {
     2         //利用Entry构造函数构建一个新节点 newEntry,
     3         Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
     4         //修改newEntry的前后节点的引用,确保其链表的引用关系是正确的
     5         newEntry.previous.next = newEntry;
     6         newEntry.next.previous = newEntry;
     7         //容量+1
     8         size++;
     9         //修改次数+1
    10         modCount++;
    11         return newEntry;
    12     }
    • 在addBefore方法中无非就是做了这件事:构建一个新节点newEntry,然后修改其前后的引用。

      5、JDK7

        在JDK 1.7,1.6的headerEntry循环链表被替换成了first和last组成的非循环链表。

     1     transient int size = 0;
     2  
     3     /**
     4      * Pointer to first node.
     5      * Invariant: (first == null && last == null) ||
     6      *            (first.prev == null && first.item != null)
     7      */
     8     transient Node<E> first;
     9  
    10     /**
    11      * Pointer to last node.
    12      * Invariant: (first == null && last == null) ||
    13      *            (last.next == null && last.item != null)
    14      */
    15     transient Node<E> last;
    • 在初始化的时候,不用去new一个Entry。

        

    1     /**
    2      * Constructs an empty list.
    3      */
    4     public LinkedList() {
    5     }
    • 在插入/删除的时候,也是默认在链尾操作。把插入的obj当成newLast,挂在oldLast的后面。另外还要先判断first是否为空,如果为空则first = obj。

      

      如下面的插入方法linkLast,在尾部操作,只需要把obj_3.next指向obj_4即可。

     1     void linkLast(E e) {
     2         final Node<E> l = last;
     3         final Node<E> newNode = new Node<>(l, e, null);
     4         last = newNode;
     5         if (l == null)
     6             first = newNode;
     7         else
     8             l.next = newNode;
     9         size++;
    10         modCount++;
    11     }

    其中

     1  private static class Node<E> {
     2           E item;
     3           Node<E> next;
     4           Node<E> prev;
     5   
     6           Node(Node<E> prev, E element, Node<E> next) {
     7               this.item = element;
     8               this.next = next;
     9               this.prev = prev;
    10          }
    11      }

      6、【1.6-header循环链表】 V.S 【1.7-first/last非循环链表】

        JDK 1.7中的first/last对比以前的header有下面几个好处:

        (1) first / last有更清晰的链头、链尾概念,代码看起来更容易明白。

        (2)first / last方式能节省new一个headerEntry。(实例化headerEntry是为了让后面的方法更加统一,否则会多很多header的空校验)

        (3)在链头/尾进行插入/删除操作,first /last方式更加快捷。

        插入/删除操作按照位置,分为两种情况:中间 和 两头。

        在中间插入/删除,两者都是一样,先遍历找到index,然后修改链表index处两头的指针。
        在两头,对于循环链表来说,由于首尾相连,还是需要处理两头的指针。而非循环链表只需要处理一边first.previous/last.next,所以理论上非循环链表更高效。      恰恰在两头(链头/链尾) 操作是最普遍的

        (对于遍历来说,两者都是链表指针循环,所以遍历效率是一样的。)

    十、线程安全性

      线程安全的概念不再赘述。分析以下场景:
      若有线程 T1 对 LinkedList 进行遍历,同时线程 T2 对其进行结构性修改。
     
      对 LinkedList 的遍历是通过 listIterator(index) 方法实现的,如下:
     1     public ListIterator<E> listIterator(int index) {
     2         checkPositionIndex(index);
     3         return new ListItr(index);
     4     }
     5 
     6 
     7     private class ListItr implements ListIterator<E> {
     8         private Node<E> lastReturned;
     9         private Node<E> next;
    10         private int nextIndex;
    11         // 初始化时二者是相等的
    12         private int expectedModCount = modCount;
    13 
    14 
    15         ListItr(int index) {
    16             // assert isPositionIndex(index);
    17             next = (index == size) ? null : node(index);
    18             nextIndex = index;
    19         }
    20 
    21 
    22         public E next() {
    23             checkForComodification();
    24             if (!hasNext())
    25                 throw new NoSuchElementException();
    26 
    27 
    28             lastReturned = next;
    29             next = next.next;
    30             nextIndex++;
    31             return lastReturned.item;
    32         }
    33 
    34 
    35         public void remove() {
    36             checkForComodification();
    37             if (lastReturned == null)
    38                 throw new IllegalStateException();
    39 
    40 
    41             Node<E> lastNext = lastReturned.next;
    42             unlink(lastReturned);
    43             if (next == lastReturned)
    44                 next = lastNext;
    45             else
    46                 nextIndex--;
    47             lastReturned = null;
    48             expectedModCount++;
    49         }
    50 
    51 
    52         // ...
    53         
    54         // 是否有其他线程对当前对象进行结构修改
    55         final void checkForComodification() {
    56             if (modCount != expectedModCount)
    57                 throw new ConcurrentModificationException();
    58         }
    59 }
      该类的 next(), add(e) 等方法在执行时会检测 modCount 与创建时是否一致(checkForComodification() 方法),从而判断是否有其他线程对该对象进行了结构修改,若有则抛出 ConcurrentModificationException 异常。
     
      因此,LinkedList 是线程不安全的。
     

    十一、总结

      1、LinkedList 内部是【双向链表】,同时实现了 List  接口 和 Deque 接口,因此也具备 List、双端队列和栈的性质。
      2、线程不安全。
  • 相关阅读:
    JAVA-基础-接口
    JAVA-基础-抽象类
    JAVA-基础-多态
    JAVA-基础-继承
    JAVA-基础-封装
    JAVA-基础-面向对象
    JAVA-基础-ArrayList集合
    CenOS 6.5下 mysql自动备份
    Cenos 6.5上的subverion的yum配置笔记
    [CF628D]Magic Numbers 题解
  • 原文地址:https://www.cnblogs.com/niujifei/p/14675805.html
Copyright © 2020-2023  润新知