API源码学习之集合--LinkedList
Posted Java小白闯天下
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了API源码学习之集合--LinkedList相关的知识,希望对你有一定的参考价值。
继续集合源码学习--LinkedList
1、该类主要成员变量及构造方法有如下几个:
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; 16 17 /** 18 * Constructs an empty list. 19 */ 20 public LinkedList() { 21 } 22 23 /** 24 * Constructs a list containing the elements of the specified 25 * collection, in the order they are returned by the collection‘s 26 * iterator. 27 * 28 * @param c the collection whose elements are to be placed into this list 29 * @throws NullPointerException if the specified collection is null 30 */ 31 public LinkedList(Collection<? extends E> c) { 32 this(); 33 addAll(c); 34 }
大家都知道LinkedList是链表类型结构,其存储数据的方式是通过第一个节点元素的成员变量引用至下一个节点元素,因此其标志性的成员变量就是整个链表首尾成员--first/last成员。另外还有的就是链表大小size元素。
以上变量是transient的,但在序列化的时候并不会被忽略,因为后面还有相关方法专门用于读取LinkedList对象反序列化的。
构造方法有两个,一个是默认的无参构造器,另一个是可传入一个集合并将该集合存入LinkedList的有参构造器。
2、核心内部类--Node、ListItr迭代器
链表结构如其名,其中是通过上下节点间的互相引用构成的数据结构关系。而其中关于节点的构建则是由LinkedList的核心内部类Node类来完成。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类有三个成员变量----item(节点实际存储的对象或数据)、next(下一个节点的引用)、prev(上一个节点的引用)
一个有参构造函数,用于初始化以上三个成员变量。
LinkedList还提供了内部迭代器的功能:
迭代器支持向前或向后迭代,迭代过程中支持对节点的增删改,同时过程中也会对并发进行检测(checkForComodification())。
具体源码如下:
1 private class ListItr implements ListIterator<E> { 2 //成员变量--lastReturned:最后返回的节点 3 private Node<E> lastReturned = null; 4 //next:指针指向的下一个节点 5 private Node<E> next; 6 //nextIndex:指针指向的下一个节点的下标 7 private int nextIndex; 8 //expectedModCount:操作数modCount期待值,用于检测并发 9 private int expectedModCount = modCount; 10 11 //构造函数,根据传入的参数初始化迭代器,将指针指向对应的下标的节点 12 ListItr(int index) { 13 // assert isPositionIndex(index); 14 next = (index == size) ? null : node(index); 15 nextIndex = index; 16 } 17 18 //判断是否还有下一个元素,通过判断指针指向的下一个节点的下标是否小于链表的大小 19 public boolean hasNext() { 20 return nextIndex < size; 21 } 22 23 //指针向后移动方法 24 public E next() { 25 //并发检测 26 checkForComodification(); 27 //检测是否仍有下一个元素,若已经没有了,那么抛出NoSuchElementException 28 if (!hasNext()) 29 throw new NoSuchElementException(); 30 31 //使最后返回的节点赋值为指针指向的下一个 32 lastReturned = next; 33 //指针向后移动 34 next = next.next; 35 //下标++ 36 nextIndex++; 37 //将向后移动了一步之后的lastReturned的存储对象item返回给方法调用者 38 return lastReturned.item; 39 } 40 41 //判断是否还有下一个元素,通过判断指针指向的下一个节点的下标是否大于0 42 public boolean hasPrevious() { 43 return nextIndex > 0; 44 } 45 46 //指针向前移动方法 47 public E previous() { 48 //并发检测 49 checkForComodification(); 50 //检测是否仍有上一个元素,若已经没有了,那么抛出NoSuchElementException 51 if (!hasPrevious()) 52 throw new NoSuchElementException(); 53 54 //判断next是否为null,若为null说明目前指针已在队尾的后面,那么则将lastReturned和next指针都赋值为队尾节点,否则赋值为next.prev 55 //next指针此时与最后返回节点lastReturned指向同一对象 56 lastReturned = next = (next == null) ? last : next.prev; 57 //下标-- 58 nextIndex--; 59 //将向前移动了一步之后的lastReturned的存储对象item返回给方法调用者 60 return lastReturned.item; 61 } 62 63 //返回此时指针指向的下一个节点的下标 64 public int nextIndex() { 65 return nextIndex; 66 } 67 68 //返回此时指针指向的节点的前一个节点的下标 69 public int previousIndex() { 70 return nextIndex - 1; 71 } 72 73 //移除节点的方法 74 public void remove() { 75 //并发检测 76 checkForComodification(); 77 //判断lastReturned是否为null,否则抛出IllegalStateException 78 //因为每次remove、add之后,都会将lastReturned的值置null 79 //这是因为为了释放被移除的元素内存,必须将lastReturned置null 80 //所以每次remove()或者add()之前都必须调用一次next()或者previous() 81 if (lastReturned == null) 82 throw new IllegalStateException(); 83 //价格lastReturned(肯定不为null)的下一个节点 84 Node<E> lastNext = lastReturned.next; 85 //随后移除最后返回的节点lastReturned 86 unlink(lastReturned); 87 //判断next指针与lastReturned是否指向同一节点,若相等(previous()指针前移后两者指向同一节点),则将指针指向最后返回节点的下一个节点 88 if (next == lastReturned) 89 next = lastNext; 90 //否则则不需要移动指针,直接将将指向指针下标-- 91 else 92 nextIndex--; 93 //将lastReturned置null 94 lastReturned = null; 95 //操作数++ 96 expectedModCount++; 97 } 98 99 //修改最后返回的节点的存储对象为传入对象 100 public void set(E e) { 101 //lastReturned若为null,说明状态位不对,在最后一次增或删操作后未进行指针移动,抛出IllegalStateException 102 if (lastReturned == null) 103 throw new IllegalStateException(); 104 //并发检测 105 checkForComodification(); 106 //修改 107 lastReturned.item = e; 108 } 109 110 public void add(E e) { 111 checkForComodification(); 112 //将lastReturned置null,若不置null,那么lastReturned指向的元素和next指向的元素间就会有一个新增的元素, 113 //为避免迭代混乱,对链表结构进行增删前必须使用移动一次指针的使用原则,以及及时清除无用的引用的设计理念,故此处将lastReturned置null 114 lastReturned = null; 115 //如果next为null,那么直接加到队尾 116 if (next == null) 117 linkLast(e); 118 //否则插入到next指针指向的节点前面 119 else 120 linkBefore(e, next); 121 //指向的下标++ 122 nextIndex++; 123 //modCount期望值++ 124 expectedModCount++; 125 } 126 127 //检测并发,将迭代器内部和链表本身的操作数进行比较,若不一致说明出现了并发情况,抛出ConcurrentModificationException 128 final void checkForComodification() { 129 if (modCount != expectedModCount) 130 throw new ConcurrentModificationException(); 131 } 132 }
通过以下方法去调用:
1 public ListIterator<E> listIterator(int index) { 2 checkPositionIndex(index); 3 return new ListItr(index); 4 }
同时还有向下兼容的迭代器类:
1 /** 2 * Adapter to provide descending iterators via ListItr.previous 3 */ 4 private class DescendingIterator implements Iterator<E> { 5 private final ListItr itr = new ListItr(size()); 6 public boolean hasNext() { 7 return itr.hasPrevious(); 8 } 9 public E next() { 10 return itr.previous(); 11 } 12 public void remove() { 13 itr.remove(); 14 } 15 }
通过以下方法去调用:
1 /** 2 * @since 1.6 3 */ 4 public Iterator<E> descendingIterator() { 5 return new DescendingIterator(); 6 }
3、内部方法:
Ps:以下的方法都是private或者default的,属于LinkedList内部使用的方法。
1)linkFirst和linkLast
先上源码:
1 /** 2 * Links e as first element. 3 */ 4 private void linkFirst(E e) { 5 final Node<E> f = first; 6 final Node<E> newNode = new Node<>(null, e, f); 7 first = newNode; 8 if (f == null) 9 last = newNode; 10 else 11 f.prev = newNode; 12 size++; 13 modCount++; 14 } 15 16 /** 17 * Links e as last element. 18 */ 19 void linkLast(E e) { 20 final Node<E> l = last; 21 final Node<E> newNode = new Node<>(l, e, null); 22 last = newNode; 23 if (l == null) 24 first = newNode; 25 else 26 l.next = newNode; 27 size++; 28 modCount++; 29 }
这两个方法从名字上我们就可以知道这些方法是用于插入链表首尾元素的。
先分析linkFirst上分析,该方法首先将原first节点存于一个final修饰的临时变量f中去(final是因为此处的赋值是明确的,为了防止后续代码中误赋值导致功能实现出现问题故用final修饰防止赋值修改。
例如没加final且后续代码中误操作导致本应剔除的节点的引用有部分没被置null,GC无法执行相关的识别及清除,最后则会导致内存中的垃圾对象越来越多),随后将新首部节点引用赋值给LinkedList的first成员变量中去。
以上操作进行完后,判断一下原first首部节点是否为null,若为null则说明该LinkedList本身就无first节点,那么说明加入了新首部元素后该LinkedList目前仅有一个元素,那么此时就将last节点也赋值为新首部元素的引用。
(即此时LinkedList的首部和尾部节点均为同一个,由于此时链表元素个数为1个,因此逻辑上也是没有问题的。)
否则的话,则将原首部节点的prev成员变量指向新首部节点的引用,那么此操作就相当于直接将新首部节点插入到了原首部节点的前面。
加入元素后LinkedList的size++,操作数modCount++。
linkLast类似。
2)unlinkFirst和unlinkLast
1 /** 2 * Unlinks non-null first node f. 3 */ 4 private E unlinkFirst(Node<E> f) { 5 // assert f == first && f != null; 6 final E element = f.item; 7 final Node<E> next = f.next; 8 f.item = null; 9 f.next = null; // help GC 10 first = next; 11 if (next == null) 12 last = null; 13 else 14 next.prev = null; 15 size--; 16 modCount++; 17 return element; 18 } 19 20 /** 21 * Unlinks non-null last node l. 22 */ 23 private E unlinkLast(Node<E> l) { 24 // assert l == last && l != null; 25 final E element = l.item; 26 final Node<E> prev = l.prev; 27 l.item = null; 28 l.prev = null; // help GC 29 last = prev; 30 if (prev == null) 31 first = null; 32 else 33 prev.next = null; 34 size--; 35 modCount++; 36 return element; 37 }
以上方法用于解除链表的首尾部节点。
以unlinkFirst为例,先将原首部节点的item实际存储对象及next下个节点的引用赋值给方法内的临时变量,随后将首部节点的item和next置null,将首部元素从链表中剔除。
随后将链表首部节点赋值为前面存储着原首部节点next成员变量的临时变量next,使下一个节点顶上成为首部节点。
随后判断next是否为null,若有,则说明没有下一个元素了,说明链表此时暂无元素节点,那么就将last尾部节点也置null。(元素的剔除要确认所有相关的引用都必须及时有效的置null,使得GC能够及时知悉并清除无用的对象)
否则就将下个元素的prev成员变量(指向着原首部元素First)值null。随后size--,modCount++,但注意此方法最后会将存储在首部节点的对象作为方法结果return回给方法调用者。
unlinkLast类似。
注意:以上两个方法均为内部使用,故其调用前必须确定传入的节点引用确实为链表首尾部节点且不为空。
3)linkBefore
1 /** 2 * Inserts element e before non-null Node succ. 3 */ 4 void linkBefore(E e, Node<E> succ) { 5 // assert succ != null; 6 final Node<E> pred = succ.prev; 7 final Node<E> newNode = new Node<>(pred, e, succ); 8 succ.prev = newNode; 9 if (pred == null) 10 first = newNode; 11 else 12 pred.next = newNode; 13 size++; 14 modCount++; 15 }
该方法用于将对象插入值一个指定的非空的节点前面。
该方法先将指定节点succ的prev上个元素的引用赋值给方法内临时变量pred(若指定元素为null,那么会报空指针异常),随后根据传入的对象实例化新节点newNode,并将succ的prev指向newNode。
判断pred是否为null,若为null,说明该节点并无上一节点,那么说明指定节点为原来的首部节点,那么此时就应该将链表的首部节点赋值为新节点newNode。
否则的话,则让原上一个元素的next指向newNode,使得newNode加入到链表中来。size++后modCount++,方法结束。
4)unlink
1 /** 2 * Unlinks non-null node x. 3 */ 4 E unlink(Node<E> x) { 5 // assert x != null; 6 final E element = x.item; 7 final Node<E> next = x.next; 8 final Node<E> prev = x.prev; 9 10 if (prev == null) { 11 first = next; 12 } else { 13 prev.next = next; 14 x.prev = null; 15 } 16 17 if (next == null) { 18 last = prev; 19 } else { 20 next.prev = prev; 21 x.next = null; 22 } 23 24 x.item = null; 25 size--; 26 modCount++; 27 return element; 28 }
该方法用于将指定的非空元素从链表中剔除。
先将节点x的item、next、prev属性赋值给方法内的final临时变量。随后判断prev和next是否为null,若有某个为null,说明该节点的上一个(或下一个)节点不存在,那么说明该节点为first(或last)节点,
所以first(或last)节点应该重新赋值为原first节点的next(或原last节点的prev)。
否则的话则将指定的非空节点x的上下节点的引用next和prev重新定向一下,使得x节点的prev节点的next指向x节点的next节点,next节点的prev指向x节点的prev节点,同时将x的next和prev置null。
(以防之后对x的上下节点剔除操作后无法被GC及时清理,因为到那个时候可能已经无法追溯是哪个引用还在指向着这个应该被剔除的无用的节点了)
随后x的item置null,释放无用引用。size--,modCount++,并将element(存储着x的item的临时变量)返回给方法调用者。
4、常用方法:
1)getFirst和getLast
1 /** 2 * Returns the first element in this list. 3 * 4 * @return the first element in this list 5 * @throws NoSuchElementException if this list is empty 6 */ 7 public E getFirst() { 8 final Node<E> f = first; 9 if (f == null) 10 throw new NoSuchElementException(); 11 return f.item; 12 } 13 14 /** 15 * Returns the last element in this list. 16 * 17 * @return the last element in this list 18 * @throws NoSuchElementException if this list is empty 19 */ 20 public E getLast() { 21 final Node<E> l = last; 22 if (l == null) 23 throw new NoSuchElementException(); 24 return l.item; 25 }
这两个方法用于获取linkedList链表的首尾部节点的item!注意是item而非节点node本身!
先将首部节点引用赋值给方法内一个final临时变量,随后判断该变量是否为null,若为null则抛出NoSuchElementException。
否则则将临时变量的item返回给方法调用者。
2)removeFirst和removeLast
1 /** 2 * Removes and returns the first element from this list. 3 * 4 * @return the first element from this list 5 * @throws NoSuchElementException if this list is empty 6 */ 7 public E removeFirst() { 8 final Node<E> f = first; 9 if (f == null) 10 throw new NoSuchElementException(); 11 return unlinkFirst(f); 12 } 13 14 /** 15 * Removes and returns the last element from this list. 16 * 17 * @return the last element from this list 18 * @throws NoSuchElementException if this list is empty 19 */ 20 public E removeLast() { 21 final Node<E> l = last; 22 if (l == null) 23 throw new NoSuchElementException(); 24 return unlinkLast(l); 25 }
见名知意的两个方法,用于将首尾部节点从linkedList中移除。
以removeFirst为例:
该方法先将first元素引用赋值给方法内的一个final局部变量f中,判断f是否为null,若为null,若为null则抛出NoSuchElementException。
否则则调用unlinkFirst将该首部元素从链表中剔除。
removeLast类似。
3)addFirst和addLast
1 /** 2 * Inserts the specified element at the beginning of this list. 3 * 4 * @param e the element to add 5 */ 6 public void addFirst(E e) { 7 linkFirst(e); 8 } 9 10 /** 11 * Appends the specified element to the end of this list. 12 * 13 * <p>This method is equivalent to {@link #add}. 14 * 15 * @param e the element to add 16 */ 17 public void addLast(E e) { 18 linkLast(e); 19 }
没什么好说的,方法内调用了内部方法linkFirst和linkLast而已,此处是通过接口层方法调用应用层方法实现封装?这种看似多此一举的写法具体原因尚不明确。
4)contains和size
1 /** 2 * Returns {@code true} if this list contains the specified element. 3 * More formally, returns {@code true} if and only if this list contains 4 * at least one element {@code e} such that 5 * <tt>(o==null ? e==null : o.equals(e))</tt>. 6 * 7 * @param o element whose presence in this list is to be tested 8 * @return {@code true} if this list contains the specified element 9 */ 10 public boolean contains(Object o) { 11 return indexOf(o) != -1; 12 } 13 14 /** 15 * Returns the number of elements in this list. 16 * 17 * @return the number of elements in this list 18 */ 19 public int size() { 20 return size; 21 }
contains用于判断一个链表中是否包含某个指定的对象,其返回了indexOf(o) != -1(indexOf返回元素在链表中的下标,若不存在则返回-1)的boolean值来判断元素存不存在。
size()则是直接将size返回。
5)indexOf和lastIndexOf
正如上面所说,indexOf返回元素在链表中(第一个匹配上的)的下标,若不存在则返回-1。而lastIndexOf则返回匹配上的元素最后位置的下标。
indexOf()中,先定义一个局部变量index,初始化为0。
然后判断传入的参数Object o是否为null。若为null,for循环从first节点开始遍历链表,查找item为null的元素的下标,每循环一次index++,查到后return index。
否则仍是循环遍历,查找o.equals(x.item)的元素,返回index下标,每循环一次index++,查到后return index。
lastIndexOf实现原理类似,只不过index初始化的值由0变成了size,循环开头由first变成了last。说白了就是从后往前遍历。
1 /** 2 * Returns the index of the first occurrence of the specified element 3 * in this list, or -1 if this list does not contain the element. 4 * More formally, returns the lowest index {@code i} such that 5 * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>, 6 * or -1 if there is no such index. 7 * 8 * @param o element to search for 9 * @return the index of the first occurrence of the specified element in 10 * this list, or -1 if this list does not contain the element 11 */ 12 public int indexOf(Object o) { 13 int index = 0; 14 if (o == null) { 15 for (Node<E> x = first; x != null; x = x.next) { 16 if (x.item == null) 17 return index; 18 index++; 19 } 20 } else { 21 for (Node<E> x = first; x != null; x = x.next) { 22 if (o.equals(x.item)) 23 return index; 24 index++; 25 } 26 } 27 return -1; 28 } 29 30 /** 31 * Returns the index of the last occurrence of the specified element 32 * in this list, or -1 if this list does not contain the element. 33 * More formally, returns the highest index {@code i} such that 34 * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>, 35 * or -1 if there is no such index. 36 * 37 * @param o element to search for 38 * @return the index of the last occurrence of the specified element in 39 * this list, or -1 if this list does not contain the element 40 */ 41 public int lastIndexOf(Object o) { 42 int index = size; 43 if (o == null) { 44 for (Node<E> x = last; x != null; x = x.prev) { 45 index--; 46 if (x.item == null) 47 return index; 48 } 49 } else { 50 for (Node<E> x = last; x != null; x = x.prev) { 51 index--; 52 if (o.equals(x.item)) 53 return index; 54 } 55 } 56 return -1; 57 }
6) add、remove、addAll、clear
1 /** 2 * Appends the specified element to the end of this list. 3 * 4 * <p>This method is equivalent to {@link #addLast}. 5 * 6 * @param e element to be appended to this list 7 * @return {@code true} (as specified by {@link Collection#add}) 8 */ 9 public boolean add(E e) { 10 linkLast(e); 11 return true; 12 } 13 14 /** 15 * Removes the first occurrence of the specified element from this list, 16 * if it is present. If this list does not contain the element, it is 17 * unchanged. More formally, removes the element with the lowest index 18 * {@code i} such that 19 * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt> 20 * (if such an element exists). Returns {@code true} if this list 21 * contained the specified element (or equivalently, if this list 22 * changed as a result of the call). 23 * 24 * @param o element to be removed from this list, if present 25 * @return {@code true} if this list contained the specified element 26 */ 27 public boolean remove(Object o) { 28 if (o == null) { 29 for (Node<E> x = first; x != null; x = x.next) { 30 if (x.item == null) { 31 unlink(x); 32 return true; 33 } 34 } 35 } else { 36 for (Node<E> x = first; x != null; x = x.next) { 37 if (o.equals(x.item)) { 38 unlink(x); 39 return true; 40 } 41 } 42 } 43 return false; 44 }
add方法就是调用了linkLast()方法,若无报错异常,则返回true。
remove(Object o)的实现写法类似indexOf(),先判断传入参数o是否为null,随后从头遍历链表,将匹配上的元素调用unlink()方法将元素从链表中移除。若未找到匹配元素,则返回false。
以上是不涉及下标定位增删方法,下面介绍涉及下标的:
/** * 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)); } /** * Removes the element at the specified position in this list. Shifts any * subsequent elements to the left (subtracts one from their indices). * Returns the element that was removed from the list. * * @param index the index of the element to be removed * @return the element previously at the specified position * @throws IndexOutOfBoundsException {@inheritDoc} */ public E remove(int index) { checkElementIndex(index); return unlink(node(index)); }
add方法调用了checkPositionIndex(index)方法,remove方法调用了checkElementIndex(index),这俩方法都是判断传入下标是否合法,具体实现可见如下源码(以下方法均为私有):
1 /** 2 * Constructs an IndexOutOfBoundsException detail message. 3 * Of the many possible refactorings of the error handling code, 4 * this "outlining" performs best with both server and client VMs. 5 */ 6 private String outOfBoundsMsg(int index) { 7 return "Index: "+index+", Size: "+size; 8 } 9 10 private void checkElementIndex(int index) { 11 if (!isElementIndex(index)) 12 throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); 13 } 14 15 private void checkPositionIndex(int index) { 16 if (!isPositionIndex(index)) 17 throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); 18 }
1 /** 2 * Tells if the argument is the index of an existing element. 3 */ 4 private boolean isElementIndex(int index) { 5 return index >= 0 && index < size; 6 } 7 8 /** 9 * Tells if the argument is the index of a valid position for an 10 * iterator or an add operation. 11 */ 12 private boolean isPositionIndex(int index) { 13 return index >= 0 && index <= size; 14 }
Ps:顺带介绍了outOfBoundsMsg()方法,该方法用于给出当IndexOutOfBoundsException被抛出时,返回一个传入参数index的值及当前链表的大小的size值字符串结果,供开发人员调试及查找报错原因。
checkElementIndex(int index)调用了isElementIndex(int index),判断传入下标是否合法。其功能其实是通过判断index是否在0到size区间来实现的。
而checkPositionIndex(int index)则调用了isPositionIndex(int index)方法,其实现与上面类似。两者区别在于,PositionIndex判断区间为[0,size],ElementIndex判断区间为[0,size)。
因为定义上PositionIndex表示可插入的位置下标,而ElementIndex表示现有的元素下标。因为可插入的下标区间为0-size,而当前存在的元素下标区间为0-(size-1)。
简单的说就是PositionIndex用于给add类型方法提供下标合法判断信息,而ElementIndex则给其余类型方法提供下标合法判断信息。
再回到关于add和remove的分析,判断完下标合法性之后,add方法再判断下传入的index是否等于size,若为size,则直接在队尾加入元素即可,于是调用linkLast()方法;否则则调用常用的linkBefore方法即可。
而remove则是直接调用unlink()方法剔除元素。
addAll(Collection<? extends E> c)方法在前面介绍LinkedList有参构造器的时候提到过,它就是调用addAll(Collection<? extends E> c),用于批量加入传入集合元素。源码如下:
/** * Appends all of the elements in the specified collection to the end of * this list, in the order that they are returned by the specified * collection‘s iterator. The behavior of this operation is undefined if * the specified collection is modified while the operation is in * progress. (Note that this will occur if the specified collection is * this list, and it‘s nonempty.) * * @param c collection containing elements to be added to this list * @return {@code true} if this list changed as a result of the call * @throws NullPointerException if the specified collection is null */ public boolean addAll(Collection<? extends E> c) { return addAll(size, c); } /** * Inserts all of the elements in the specified collection into this * list, starting at the specified position. Shifts the element * currently at that position (if any) and any subsequent elements to * the right (increases their indices). The new elements will appear * in the list in the order that they are returned by the * specified collection‘s iterator. * * @param index index at which to insert the first element * from the specified collection * @param c collection containing elements to be added to this list * @return {@code true} if this list changed as a result of the call * @throws IndexOutOfBoundsException {@inheritDoc} * @throws NullPointerException if the specified collection is null */ public boolean addAll(int index, Collection<? extends E> c) { checkPositionIndex(index); Object[] a = c.toArray(); int numNew = a.length; if (numNew == 0) return false; Node<E> pred, succ; if (index == size) { succ = null; pred = last; } else { succ = node(index); pred = succ.prev; } for (Object o : a) { @SuppressWarnings("unchecked") E e = (E) o; Node<E> newNode = new Node<>(pred, e, null); if (pred == null) first = newNode; else pred.next = newNode; pred = newNode; } if (succ == null) { last = pred; } else { pred.next = succ; succ.prev = pred; } size += numNew; modCount++; return true; }
addAll(Collection<? extends E> c)调用了addAll(int index, Collection<? extends E> c)这个方法,该方法实现原理如下:
先将传入的集合通过toArray()方法将集合转化成数组,然后将数组长度赋值给局部变量numNew,若其为0,则说明传入的集合无元素,返回false。
若不为0:
声明两个节点变量succ和pred,分别代表即将插入位置的当前节点和前一个节点。
若传入的index下标==size,说明插入位置在队尾,则succ为null,pred为last队尾元素。否则succ为当前插入下标位置的节点,pred为succ的prev。
(Ps:succ的获取是通过node(int index)方法获取的,该方法源码如下:
1 /** 2 * Returns the (non-null) Node at the specified element index. 3 */ 4 Node<E> node(int index) { 5 // assert isElementIndex(index); 6 7 if (index < (size >> 1)) { 8 Node<E> x = first; 9 for (int i = 0; i < index; i++) 10 x = x.next; 11 return x; 12 } else { 13 Node<E> x = last; 14 for (int i = size - 1; i > index; i--) 15 x = x.prev; 16 return x; 17 } 18 }
node(index)方法用于获取指定下标的节点,由于链表本身节点并无关于index下标的设计,故其主要是通过遍历链表来寻找对应下标的元素。具体实现为:
方法先判断size是否小于size的一半(index < (size >> 1)),
若小于,则说明指定下标节点在链表前半部分,那么就从队首开始遍历,否则从队尾开始遍历节点,找到对应位置的节点后返回给调用者。
由于链表的这种查找元素的方式,设计人员对链表查找采用了前后两头双向查找的方式来提高效率,但即便如此,若是链表队列长度很长的话,最长的遍历次数也依旧要到达size/2,效率依旧不高,这就体现了linkedList的查找慢的特点了。
)
继续回到linkedList的addAll方法,当获得了插入位置的当前节点及其上一节点后,那么则遍历数组:
将数组元素转型为linkedList的泛型类型E后,实例化一个新节点newNode,并判断pred是否为null,若为null说明在队首插入集合元素,那么将newNode赋值给first节点;
否则赋值给pred.next,并让pred= pred.next,进而开始下一次循环。
随后判断当前插入位置节点succ是否为null,若为null则说明集合被插入到了队尾,那么让赋值last为pred(此时pred经过一轮循环已经被赋值为新加入的集合的尾部节点了);
否则让succ.prev = pred。
此处可见不论加入的集合元素个数有多少,均只需将插入位置的节点及其前一个节点的链接断开,遍历一下通过链接将新加入节点串联起来(for循环效率极快),再将加入节点的尾部与succ串联起来即可。
不论元素个数多少,仅需断开和链接一下插入节点及前节点即可,相比于ArrayList的插入(需要调用System.arraycopy(),底层使用C语言进行内存块的复制移动,牵一发则动全身),效率很高,体现了linkedList的插入快的特点。
clear()方法用于将链表清空:
1 /** 2 * Removes all of the elements from this list. 3 * The list will be empty after this call returns. 4 */ 5 public void clear() { 6 // Clearing all of the links between nodes is "unnecessary", but: 7 // - helps a generational GC if the discarded nodes inhabit 8 // more than one generation 9 // - is sure to free memory even if there is a reachable Iterator 10 for (Node<E> x = first; x != null; ) { 11 Node<E> next = x.next; 12 x.item = null; 13 x.next = null; 14 x.prev = null; 15 x = next; 16 } 17 first = last = null; 18 size = 0; 19 modCount++; 20 }
该方法会从头遍历整个链表,先将每个节点的next引用赋值给一个临时变量,随后将该节点的item、prev、next清空,然后再继续下一个节点直到末尾。
而后还需将first、last引用置null,size置0,modCount++。
7)get、set
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 /** 14 * Replaces the element at the specified position in this list with the 15 * specified element. 16 * 17 * @param index index of the element to replace 18 * @param element element to be stored at the specified position 19 * @return the element previously at the specified position 20 * @throws IndexOutOfBoundsException {@inheritDoc} 21 */ 22 public E set(int index, E element) { 23 checkElementIndex(index); 24 Node<E> x = node(index); 25 E oldVal = x.item; 26 x.item = element; 27 return oldVal; 28 }
此两个方法用于对指定下标节点的元素item进行读写操作:
a.get方法首先判断传入下标的合法性,若未报错则调用node方法返回对应下标节点的item;
b.set方法同样的也会先判断传入下标的合法性(注意,此处和add不一样,set方法仅能改变现有节点的存储的对象或数据,并不是新增,故这两个方法都是判断elementIndex,即是否为0-(size-1)),
随后通过node方法获取对应下标的节点,将旧item存入一个临时变量oldVal中去,随后改变item的值为新值,并将oldVal返回给方法调用者。
7)Queue单向队列,先进先出。方法:
peek()
1 /** 2 * Retrieves, but does not remove, the head (first element) of this list. 3 * 4 * @return the head of this list, or {@code null} if this list is empty 5 * @since 1.5 6 */ 7 public E peek() { 8 final Node<E> f = first; 9 return (f == null) ? null : f.item; 10 }
peek()方法返回第一个节点存放的元素/对象,若首部节点不存在则返回null。用于供调用者看一眼队首节点的元素。
element()
1 /** 2 * Retrieves, but does not remove, the head (first element) of this list. 3 * 4 * @return the head of this list 5 * @throws NoSuchElementException if this list is empty 6 * @since 1.5 7 */ 8 public E element() { 9 return getFirst(); 10 }
element(),调用getFirst()方法返回队首元素给方法调用者。与peek()不同的是,getFirst方法会抛出NoSuchElementException。
poll()
1 /** 2 * Retrieves and removes the head (first element) of this list. 3 * 4 * @return the head of this list, or {@code null} if this list is empty 5 * @since 1.5 6 */ 7 public E poll() { 8 final Node<E> f = first; 9 return (f == null) ? null : unlinkFirst(f); 10 }
poll()方法用于将队首元素返回给方法调用者并将队首从队列中剔除。简单形象地“拉”出来。
remove()
1 /** 2 * Retrieves and removes the head (first element) of this list. 3 * 4 * @return the head of this list 5 * @throws NoSuchElementException if this list is empty 6 * @since 1.5 7 */ 8 public E remove() { 9 return removeFirst(); 10 }
remove()方法作用域poll相同,区别在于同样地会抛出NoSuchElementException。
offer()
1 /** 2 * Adds the specified element as the tail (last element) of this list. 3 * 4 * @param e the element to add 5 * @return {@code true} (as specified by {@link Queue#offer}) 6 * @since 1.5 7 */ 8 public boolean offer(E e) { 9 return add(e); 10 }
offer()方法调用add()方法将元素添加进队列的尾部。
8)Deque双向队列方法:
offerFirst()、offerLast()
1 /** 2 * Inserts the specified element at the front of this list. 3 * 4 * @param e the element to insert 5 * @return {@code true} (as specified by {@link Deque#offerFirst}) 6 * @since 1.6 7 */ 8 public boolean offerFirst(E e) { 9 addFirst(e); 10 return true; 11 } 12 13 /** 14 * Inserts the specified element at the end of this list. 15 * 16 * @param e the element to insert 17 * @return {@code true} (as specified by {@link Deque#offerLast}) 18 * @since 1.6 19 */ 20 public boolean offerLast(E e) { 21 addLast(e); 22 return true; 23 }
offerFirst()方法用于将元素从队首添加进队列,调用了addFirst()方法。
offerLast()方法用于将元素从队尾添加进队列,调用了addLast()方法。
peekFirst()和peekLast()
1 /** 2 * Retrieves, but does not remove, the first element of this list, 3 * or returns {@code null} if this list is empty. 4 * 5 * @return the first element of this list, or {@code null} 6 * if this list is empty 7 * @since 1.6 8 */ 9 public E peekFirst() { 10 final Node<E> f = first; 11 return (f == null) ? null : f.item; 12 } 13 14 /** 15 * Retrieves, but does not remove, the last element of this list, 16 * or returns {@code null} if this list is empty. 17 * 18 * @return the last element of this list, or {@code null} 19 * if this list is empty 20 * @since 1.6 21 */ 22 public E peekLast() { 23 final Node<E> l = last; 24 return (l == null) ? null : l.item; 25 }
这两个方法用于查看此时队列中队首和队尾节点的元素。
pollFirst()和pollLast()方法:
1 /** 2 * Retrieves and removes the first element of this list, 3 * or returns {@code null} if this list is empty. 4 * 5 * @return the first element of this list, or {@code null} if 6 * this list is empty 7 * @since 1.6 8 */ 9 public E pollFirst() { 10 final Node<E> f = first; 11 return (f == null) ? null : unlinkFirst(f); 12 } 13 14 /** 15 * Retrieves and removes the last element of this list, 16 * or returns {@code null} if this list is empty. 17 * 18 * @return the last element of this list, or {@code null} if 19 * this list is empty 20 * @since 1.6 21 */ 22 public E pollLast() { 23 final Node<E> l = last; 24 return (l == null) ? null : unlinkLast(l); 25 }
类似地,这两个方法也是用于将队首或队尾节点元素从队列中“拉“出来,实现方式与前面的poll类似。
push()和pop()
1 /** 2 * Pushes an element onto the stack represented by this list. In other 3 * words, inserts the element at the front of this list. 4 * 5 * <p>This method is equivalent to {@link #addFirst}. 6 * 7 * @param e the element to push 8 * @since 1.6 9 */ 10 public void push(E e) { 11 addFirst(e); 12 } 13 14 /** 15 * Pops an element from the stack represented by this list. In other 16 * words, removes and returns the first element of this list. 17 * 18 * <p>This method is equivalent to {@link #removeFirst()}. 19 * 20 * @return the element at the front of this list (which is the top 21 * of the stack represented by this list) 22 * @throws NoSuchElementException if this list is empty 23 * @since 1.6 24 */ 25 public E pop() { 26 return removeFirst(); 27 }
push()、pop()方法分别调用了addFirst和removeFirst实现对队列首部元素的增加和删除操作。
removeFirstOccurrence()和removeLastOccurrence()
1 /** 2 * Removes the first occurrence of the specified element in this 3 * list (when traversing the list from head to tail). If the list 4 * does not contain the element, it is unchanged. 5 * 6 * @param o element to be removed from this list, if present 7 * @return {@code true} if the list contained the specified element 8 * @since 1.6 9 */ 10 public boolean removeFirstOccurrence(Object o) { 11 return remove(o); 12 } 13 14 /** 15 * Removes the last occurrence of the specified element in this 16 * list (when traversing the list from head to tail). If the list 17 * does not contain the element, it is unchanged. 18 * 19 * @param o element to be removed from this list, if present 20 * @return {@code true} if the list contained the specified element 21 * @since 1.6 22 */ 23 public boolean removeLastOccurrence(Object o) { 24 if (o == null) { 25 for (Node<E> x = last; x != null; x = x.prev) { 26 if (x.item == null) { 27 unlink(x); 28 return true; 29 } 30 } 31 } else { 32 for (Node<E> x = last; x != null; x = x.prev) { 33 if (o.equals(x.item)) { 34 unlink(x); 35 return true; 36 } 37 } 38 } 39 return false; 40 }
removeFirstOccurrence()和removeLastOccurrence()用于删除第一次及最后一次出现在队列中的与传入参数匹配的节点元素。
removeFirstOccurrence()调用了remove(Object o)方法删除。
而removeLastOccurrence()采用了和remove()一样的实现原理,不同的是它是从队尾开始遍历链表的。
以上就是队列相关方法,为什么有了前面那么多的方法后还要去搞个这些队列方法呢?因为LinkedList实现了Deque接口,而Deque又继承自Queue,所以前面这些方法都是接口要求实现的,并非多此一举。
9)clone()和superClone()
1 @SuppressWarnings("unchecked") 2 private LinkedList<E> superClone() { 3 try { 4 return (LinkedList<E>) super.clone(); 5 } catch (CloneNotSupportedException e) { 6 throw new InternalError(); 7 } 8 } 9 10 /** 11 * Returns a shallow copy of this {@code LinkedList}. (The elements 12 * themselves are not cloned.) 13 * 14 * @return a shallow copy of this {@code LinkedList} instance 15 */ 16 public Object clone() { 17 LinkedList<E> clone = superClone(); 18 19 // Put clone into "virgin" state 20 clone.first = clone.last = null; 21 clone.size = 0; 22 clone.modCount = 0; 23 24 // Initialize clone with our elements 25 for (Node<E> x = first; x != null; x = x.next) 26 clone.add(x.item); 27 28 return clone; 29 }
superClone()仅为复制链表的引用,复制后副本的修改会影响原本的链表;
clone()则是复制一个内部节点数据一致,而与原本互不影响的副本。
clone()先调用superClone()将链表的副本引用拿到,随后将副本链表的first和last置null,并将size和modCount置0。
这样一来副本的首尾节点与本链表就分开了,并且链表此时就变成了一个全新的状态。
随后在遍历本链表将节点逐个将x.item联入副本链表,这就完成了一个clone操作。
10)toArray()
第一个toArray()返回的是Object[]数组。
内部实现是直接创建一个size大小的数组,然后将遍历链表节点内部的item逐个加入到数组中。完成后将数组返回给方法调用者。
1 public Object[] toArray() {
2 Object[] result = new Object[size];
3 int i = 0;
4 for (Node<E> x = first; x != null; x = x.next)
5 result[i++] = x.item;
6 return result;
7 }
第二个toArray()返回的是根据传入类型来构建的数组T[] a。
内部实现是先判断数组的长度是否小于链表大小,若小与链表大小,则利用反射动态实例化一个容量=size、类型为T[]的数组。
随后实例化一个临时下标变量i初始化为0并实例化一个Object[]类型数组result并赋值为传入数组a的引用,遍历链表并将链表中节点逐个添加至result中。
随后再判断a数组长度是否大于size(因为可能传入的数组a长度可能会大于size),若大于size则将a[size]置null
1 @SuppressWarnings("unchecked") 2 public <T> T[] toArray(T[] a) { 3 if (a.length < size) 4 a = (T[])java.lang.reflect.Array.newInstance( 5 a.getClass().getComponentType(), size); 6 int i = 0; 7 Object[] result = a; 8 for (Node<E> x = first; x != null; x = x.next) 9 result[i++] = x.item; 10 11 if (a.length > size) 12 a[size] = null; 13 14 return a; 15 }
11)readObject()和writeObject()
这两个方法用于序列化和反序列化一个链表对象,源码入下:
1 /** 2 * Saves the state of this {@code LinkedList} instance to a stream 3 * (that is, serializes it). 4 * 5 * @serialData The size of the list (the number of elements it 6 * contains) is emitted (int), followed by all of its 7 * elements (each an Object) in the proper order. 8 */ 9 private void writeObject(java.io.ObjectOutputStream s) 10 throws java.io.IOException { 11 // Write out any hidden serialization magic 12 s.defaultWriteObject(); 13 14 // Write out size 15 s.writeInt(size); 16 17 // Write out all elements in the proper order. 18 for (Node<E> x = first; x != null; x = x.next) 19 s.writeObject(x.item); 20 } 21 22 /** 23 * Reconstitutes this {@code LinkedList} instance from a stream 24 * (that is, deserializes it). 25 */ 26 @SuppressWarnings("unchecked") 27 private void readObject(java.io.ObjectInputStream s) 28 throws java.io.IOException, ClassNotFoundException { 29 // Read in any hidden serialization magic 30 s.defaultReadObject(); 31 32 // Read in size 33 int size = s.readInt(); 34 35 // Read in all elements in the proper order. 36 for (int i = 0; i < size; i++) 37 linkLast((E)s.readObject()); 38 }
以writeObject()为例,其先调用ObjectOutputStream的defaultWriteObject()来完成一个默认的序列化,再将transient的size通过writeInt()写入到流中,
最后按顺序遍历链表将链表中的节点item逐个写入流中。
因为链表这种特殊的集合结构,其中的节点没法作为成员变量通过默认方式序列化到流中,所以通过writeObject()可以很好地实现linkedList的序列化
readObject()类似。
以上是关于API源码学习之集合--LinkedList的主要内容,如果未能解决你的问题,请参考以下文章