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 }
这段源码其实挺好理解的,大意就是
- 创建引用l,指向原尾元素last
- 创建新结点newNode,该节点的prev指向保存的原尾元素引用l
- 尾元素last指向新结点newNode
- 若原尾元素引用l为空,说明此时刚刚添加第一个元素,之前都没有元素,first和last都为空,则首元素引用first指向新建节点newNode,此时,first、last都指向同一个元素newNode;否则,原尾元素的next指向newNode,完成新尾元素和原尾元素的连接
- 元素个数加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
2、执行add("first"),实际上是执行linkLast("first"),这个是第一个元素,此时,newNode(null, "first", null)既是首元素又是尾元素,因此最后first = last = newNode(null, "first", null),而size加1,得到1。如下图
2、接下来执行add("next"),此时首元素first已存在,需要处理的是尾元素与新元素的连接,关键点是last = newNode和l.next = newNode,即新元素结点链接到原尾元素,同时把原尾元素的next链接到新节点,完成链接,如图:
用另外一种表示方式就是:
3、执行add("last"),过程与add("next")类似,结果如下,由此可知,顺序添加元素只需要变更尾结点,因此速度较快。而且对于元素并不会进行判断,允许空、重复的元素。
指定索引插入元素
- 判断是否越界
- 若没有越界,则再判断是否是尾元素添加,若是,直接顺序添加
- 若不是,则先找到指定索引的元素结点(下一小节细讲),然后执行结点变更操作,和添加有点类似,这里就不细讲。
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种
- 指定索引删除
- 指定元素删除
指定元素删除索引,是顺序进行判断找到相应的元素的,因此效率不高,从这里也可以看到,元素可以为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 ? get(i)==null : 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)。这里大致讲一下。说白了,就是结点引用的变更,一个图就可以说明,如下。
- prev.next = next;表示上一个元素的next指向本结点的next实际元素,即跳过本结点链接到下一个结点,如左边的红箭头
- next.prev = prev;表示下一个元素的prev指向本结点的prev实际元素,即跳过本结点链接到上一个结点,如右边的红箭头。
这样便完成了链接的变更。当然这里还考虑了该元素是否是首元素(首元素first下移一格为next)、是否是尾元素(尾元素last上移一格为prev)。
首元素的情况:
尾元素的情况类似,不再赘述。
六、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的简单说明。