Java7/8集合框架——LinkedList

时间:2021-03-07 19:33:56

java.util.LinkedList

  一、LinkedList的特点及与ArrayList的比较

  二、LinkedList的内部实现

  三、LinkedList添加元素

  四、LinkedList查找元素

  五、LinkedList删除元素

  六、LinkedList修改元素

  七、Java8中的改动

 

一、LinkedList的特点及与ArrayList的比较

       对比上一篇的的ArrayList介绍,LinkedList因内部实现不同,其元素的内部访问方式是不一样。LinkedList的特点大致和ArrayList的比较如下:

关注的问题点 ArrayList相关结论 LinkedList相关结论

是否允许空的元素

是否允许重复的元素

元素有序:读取数据和存放数据的顺序一致

是否线程安全
随机访问的效率 随机访问指定索引(数组的索引)的元素快 因需要进行遍历,随机访问指定索引的元素较慢,而利用双向链表的特性,可以从两端进行访问,会使得平均访问的元素减少一半
顺序添加元素的效率

在不涉及扩容时,顺序添加元素速度快;

当需要扩容时,涉及到元素的复制,相对较慢

顺序添加元素速度快,只是新建一个结点,然后添加结点链接;

删除和插入元素的效率

因涉及到复制和移动后续的元素,相对较慢

删除和插入涉及到遍历找到相关结点,因此会慢点,但是找到结点后的相关操作会比较快,因不涉及到元素的移动

 

 

二、LinkedList的内部实现

       LinkedList是以双向链表为基础实现列表的集合,内部实现结点中,有指向上一个结点prev和下一个结点next的引用,即双向链表,而E item即为实际的存储元素,如下:

 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     }

       LinkedList内部保存了元素个数、首结点引用和尾结点引用。因此要获得LinkedList的首元素和尾元素元素也比较直接。

元素 作用
size 元素个数
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;

三、 LinkedList添加元素

  顺序添加元素

       直接上源码:

 1     /**
 2      * Constructs an empty list.
 3      * 构造一个空List
 4      */
 5     public LinkedList() {
 6     }
 7     /**
 8      * Appends the specified element to the end of this list.
 9      *
10      * <p>This method is equivalent to {@link #addLast}.
11      *
12      * @param e element to be appended to this list
13      * @return {@code true} (as specified by {@link Collection#add})
14      */
15     public boolean add(E e) {
16         linkLast(e);
17         return true;
18     }
19     /**
20      * Links e as last element.
21      * 
22      * 将新元素链接到最后一个元素
23      */
24     void linkLast(E e) {
25         final Node<E> l = last;
26         final Node<E> newNode = new Node<>(l, e, null);
27         last = newNode;
28         if (l == null)
29             first = newNode;
30         else
31             l.next = newNode;
32         size++;
33         modCount++;
34     }

  这段源码其实挺好理解的,大意就是

  1. 创建引用l,指向原尾元素last
  2. 创建新结点newNode,该节点的prev指向保存的原尾元素引用l
  3. 尾元素last指向新结点newNode
  4. 若原尾元素引用l为空,说明此时刚刚添加第一个元素,之前都没有元素,first和last都为空,则首元素引用first指向新建节点newNode,此时,first、last都指向同一个元素newNode;否则,原尾元素的next指向newNode,完成新尾元素和原尾元素的连接
  5. 元素个数加1,修改次数加1。完成

  下面以这段添加元素的代码进行图示讲解

1 public static void main(String[] args) {
2     List<String> list = new LinkedList<>();
3     list.add("first");
4     list.add("next");
5     list.add("last");
6 }

 1、首先是构造空的List,此时size=0,而first和last均为null

Java7/8集合框架——LinkedList

 2、执行add("first"),实际上是执行linkLast("first"),这个是第一个元素,此时,newNode(null, "first", null)既是首元素又是尾元素,因此最后first = last = newNode(null, "first", null),而size加1,得到1。如下图

 Java7/8集合框架——LinkedList

 2、接下来执行add("next"),此时首元素first已存在,需要处理的是尾元素与新元素的连接,关键点是last = newNode和l.next = newNode,即新元素结点链接到原尾元素,同时把原尾元素的next链接到新节点,完成链接,如图:

Java7/8集合框架——LinkedList

  用另外一种表示方式就是:

Java7/8集合框架——LinkedList

3、执行add("last"),过程与add("next")类似,结果如下,由此可知,顺序添加元素只需要变更尾结点,因此速度较快。而且对于元素并不会进行判断,允许空、重复的元素。

Java7/8集合框架——LinkedList

  指定索引插入元素

  1. 判断是否越界
  2. 若没有越界,则再判断是否是尾元素添加,若是,直接顺序添加
  3. 若不是,则先找到指定索引的元素结点(下一小节细讲),然后执行结点变更操作,和添加有点类似,这里就不细讲。
 1     /**
 2      * Inserts the specified element at the specified position in this list.
 3      * Shifts the element currently at that position (if any) and any
 4      * subsequent elements to the right (adds one to their indices).
 5      *
 6      * @param index index at which the specified element is to be inserted
 7      * @param element element to be inserted
 8      * @throws IndexOutOfBoundsException {@inheritDoc}
 9      */
10     public void add(int index, E element) {
11         checkPositionIndex(index);
12 
13         if (index == size)
14             linkLast(element);
15         else
16             linkBefore(element, node(index));
17     }
18     private void checkPositionIndex(int index) {
19         if (!isPositionIndex(index))
20             throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
21     }
22     /**
23      * Tells if the argument is the index of a valid position for an
24      * iterator or an add operation.
25      */
26     private boolean isPositionIndex(int index) {
27         return index >= 0 && index <= size;
28     }
29     /**
30      * Returns the (non-null) Node at the specified element index.
31      */
32     Node<E> node(int index) {
33         // assert isElementIndex(index);
34 
35         if (index < (size >> 1)) {
36             Node<E> x = first;
37             for (int i = 0; i < index; i++)
38                 x = x.next;
39             return x;
40         } else {
41             Node<E> x = last;
42             for (int i = size - 1; i > index; i--)
43                 x = x.prev;
44             return x;
45         }
46     }
47     /**
48      * Inserts element e before non-null Node succ.
49      */
50     void linkBefore(E e, Node<E> succ) {
51         // assert succ != null;
52         final Node<E> pred = succ.prev;
53         final Node<E> newNode = new Node<>(pred, e, succ);
54         succ.prev = newNode;
55         if (pred == null)
56             first = newNode;
57         else
58             pred.next = newNode;
59         size++;
60         modCount++;
61     }

 

 

四、LinkedList查找元素

  根据指定索引查找元素 

 1     /**
 2      * Returns the element at the specified position in this list.
 3      *
 4      * @param index index of the element to return
 5      * @return the element at the specified position in this list
 6      * @throws IndexOutOfBoundsException {@inheritDoc}
 7      */
 8     public E get(int index) {
 9         checkElementIndex(index);
10         return node(index).item;
11     }
12     /**
13      * Returns the (non-null) Node at the specified element index.
14      */
15     Node<E> node(int index) {
16         // assert isElementIndex(index);
17 
18         if (index < (size >> 1)) {
19             Node<E> x = first;
20             for (int i = 0; i < index; i++)
21                 x = x.next;
22             return x;
23         } else {
24             Node<E> x = last;
25             for (int i = size - 1; i > index; i--)
26                 x = x.prev;
27             return x;
28         }
29     }

 

  这里使用了双向链表的特性,可以向前或者向后顺序查找,即判断索引落在前半部分(index < (size >> 1)),向后索引,落在后半部分,向前索引,这就能保证最多只要遍历一半,提高效率。

 

五、LinkedList删除元素

   和ArrayList类似,删除元素也有2种

  1. 指定索引删除
  2. 指定元素删除

  指定元素删除索引,是顺序进行判断找到相应的元素的,因此效率不高,从这里也可以看到,元素可以为null,因为null(使用==)和实际元素(使用equals)的对等判断是不一样的方法;指定索引也是需要找到元素,如上一小节所述,然后再删除元素。

 1     /**
 2      * Removes the first occurrence of the specified element from this list,
 3      * if it is present.  If this list does not contain the element, it is
 4      * unchanged.  More formally, removes the element with the lowest index
 5      * {@code i} such that
 6      * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
 7      * (if such an element exists).  Returns {@code true} if this list
 8      * contained the specified element (or equivalently, if this list
 9      * changed as a result of the call).
10      *
11      * @param o element to be removed from this list, if present
12      * @return {@code true} if this list contained the specified element
13      */
14     public boolean remove(Object o) {
15         if (o == null) {
16             for (Node<E> x = first; x != null; x = x.next) {
17                 if (x.item == null) {
18                     unlink(x);
19                     return true;
20                 }
21             }
22         } else {
23             for (Node<E> x = first; x != null; x = x.next) {
24                 if (o.equals(x.item)) {
25                     unlink(x);
26                     return true;
27                 }
28             }
29         }
30         return false;
31     }
32     /**
33      * Unlinks non-null node x.
34      */
35     E unlink(Node<E> x) {
36         // assert x != null;
37         final E element = x.item;
38         final Node<E> next = x.next;
39         final Node<E> prev = x.prev;
40 
41         if (prev == null) {
42             first = next;
43         } else {
44             prev.next = next;
45             x.prev = null;
46         }
47 
48         if (next == null) {
49             last = prev;
50         } else {
51             next.prev = prev;
52             x.next = null;
53         }
54 
55         x.item = null;
56         size--;
57         modCount++;
58         return element;
59     }

 

  

 1     /**
 2      * Removes the element at the specified position in this list.  Shifts any
 3      * subsequent elements to the left (subtracts one from their indices).
 4      * Returns the element that was removed from the list.
 5      *
 6      * @param index the index of the element to be removed
 7      * @return the element previously at the specified position
 8      * @throws IndexOutOfBoundsException {@inheritDoc}
 9      */
10     public E remove(int index) {
11         checkElementIndex(index);
12         return unlink(node(index));
13     }

 

   从实现来看,最后删除元素都是直接调用了unlink(Node n)。这里大致讲一下。说白了,就是结点引用的变更,一个图就可以说明,如下。

  1. prev.next = next;表示上一个元素的next指向本结点的next实际元素,即跳过本结点链接到下一个结点,如左边的红箭头
  2. next.prev = prev;表示下一个元素的prev指向本结点的prev实际元素,即跳过本结点链接到上一个结点,如右边的红箭头。

这样便完成了链接的变更。当然这里还考虑了该元素是否是首元素(首元素first下移一格为next)、是否是尾元素(尾元素last上移一格为prev)。

 

Java7/8集合框架——LinkedList

首元素的情况:

Java7/8集合框架——LinkedList

尾元素的情况类似,不再赘述。

  

六、LinkedList修改元素

   可以到,这里也是引用了node(index),要顺序找到相应的元素,所以比较慢,但是一旦找到元素,就只会变更相关结点信息,这部分操作还是比较快的。

 1     /**
 2      * Replaces the element at the specified position in this list with the
 3      * specified element.
 4      *
 5      * @param index index of the element to replace
 6      * @param element element to be stored at the specified position
 7      * @return the element previously at the specified position
 8      * @throws IndexOutOfBoundsException {@inheritDoc}
 9      */
10     public E set(int index, E element) {
11         checkElementIndex(index);
12         Node<E> x = node(index);
13         E oldVal = x.item;
14         x.item = element;
15         return oldVal;
16     }

 

七、Java8中的改动

  本文中介绍的相关方法,在Java8没有任何的改动。

以上便是LinkedList的简单说明。