咱們除了最最經常使用的ArrayList
以外,還有LinkedList
,這究竟是什麼東西?從LinkedList官方文檔,咱們能夠了解到,它實際上是實現了List
和Queue
的雙向鏈表結構,而ArrayList
底層則是數組結構。html
下面的講解基於jdk 1.8
:java
繼承了AbstractSequentialList
,實現了List
,Queue
,Cloneable
,Serializable
,既能夠當成列表使用,也能夠當成隊列,堆棧使用。主要特色有:node
List list = Collections.synchronizedList(new LinkedList());
List
接口,能夠對它進行隊列操做Queue
接口,能夠當成堆棧或者雙向隊列使用Serializable
,能夠被序列化和反序列化下面是LinkedList
的結構,注意:指針結束指向的是node,開始的是prev
或者next
api
源碼定義以下:數組
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable{ }
成員變量相對比較簡單,由於不像ArrayList
同樣,須要使用數組保存元素,LinkedList
是靠引用來關聯先後節點,因此這裏只有大小,第一個節點,最後一個節點,以及序列化的uid。安全
// 大小 transient int size = 0; // 第一個節點 transient Node<E> first; // 最後一個節點 transient Node<E> last; // 序列化uid private static final long serialVersionUID = 876323262645176354L;
咱們來看看Node
究竟是何方神聖?
其實就是內部類,裏面的item
是真正保存節點的地方,next是下一個節點的引用,prev
是上一個節點的引用。這裏也體現了LinkedList
其實就是雙線鏈表。多線程
只有一個構造函數,三個參數分別對應三個屬性。oracle
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; } }
構造函數有兩個,一個是無參數構造函數,另外一個是初始化集合元素,裏面調用的實際上是addAll
,一看就是將裏面全部的元素加入到集合中。less
public LinkedList() { } public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
獲取第一個元素:函數
public E getFirst() { // 保存第一個元素爲f,注意是final的, final Node<E> f = first; if (f == null) // 若是沒有第一個元素,那麼就會拋出異常 throw new NoSuchElementException(); // 返回第一個元素的item return f.item; }
獲取最後一個元素,和獲取第一個的原理差很少
public E getLast() { // 保存最後一個元素的引用爲l final Node<E> l = last; // 若是爲空,拋出錯誤 if (l == null) throw new NoSuchElementException(); // 返回item return l.item; }
經過索引來獲取元素,裏面是調用了另一個方法先獲取節點,再獲取該節點的item
,在此以前,作了index
安全性校驗。
public E get(int index) { checkElementIndex(index); return node(index).item; }
在👆上面的代碼中調用了經過索引位置查找節點位置的函數,下面咱們來分析一下這個函數,因爲底層是鏈表實現的,因此呢?遍歷起來不是很方便,就考慮到位運算,若是索引位置在後面一半,就從後往前遍歷查找,不然從前日後遍歷。
Node<E> node(int index) { // assert isElementIndex(index); // size>>1 表示除以2,至關於index小於size的一半 if (index < (size >> 1)) { // 從前面開始遍歷,取出first節點,由於中間過程引用會變化,因此不可直接操做first 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; } }
查找某一個元素的索引位置,分爲兩種狀況討論,若是要查找的元素爲空,那麼就使用==
,不然使用equals()
,這也從側面印證了LinedList
其實是能夠存儲null
元素的。使用計數查找:
public int indexOf(Object o) { int index = 0; // 若是須要查找null元素 if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) return index; index++; } } else { // 查找元素不爲空 for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) return index; index++; } } return -1; }
和前面的indexOf
差很少,區別就是這個是後面開始查找,找到第一個符合的元素。
public int indexOf(Object o) { int index = 0; // 查找元素 if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) return index; index++; } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) return index; index++; } } return -1; }
將元素e,添加到第一個節點,公有方法是addFirst()
,可是其實內部調用是linkFirst()
,這是private
方法。
public void addFirst(E e) { linkFirst(e); } private void linkFirst(E e) { // 先保存第一個節點 final Node<E> f = first; // 初始化一個新節點,prev是null,next是f(以前的首節點) final Node<E> newNode = new Node<>(null, e, f); // 更新first爲新節點 first = newNode; // 若是以前的第一個節點是空的,那麼就說明裏面是空的,沒有元素 if (f == null) // 最後一個元素也是新加入的元素 last = newNode; else // f的prev前置節點的引用更新爲新的節點 f.prev = newNode; // 個數增長 size++; // 修改次數增長 modCount++; }
將元素添加在鏈表最後,其實內部也是直接調用的private
方法linkLast()
:
public void addLast(E e) { linkLast(e); } void linkLast(E e) { // 保存最後一個節點的引用 final Node<E> l = last; // 初始化一個節點,前置節點指針引用指向以前的最後一個節點,後續節點的引用是null final Node<E> newNode = new Node<>(l, e, null); // 將最後一個節點更新 last = newNode; // 若是以前的最後一個節點是null,說明鏈表是空的 if (l == null) // 新節點同時是第一個節點 first = newNode; else // 不然以前的最後一個節點的後續節點引用更新爲新的節點 l.next = newNode; // 大小+1 size++; // 修改次數+1 modCount++; }
增長元素,默認也是在鏈表的最後添加,完成返回true:
public boolean add(E e) { linkLast(e); return true; }
往鏈表裏面批量添加元素,裏面默認是在最後面批量添加,內部調用的是addAll(int index, Collection<? extends E> c)
,添加以前會判斷索引位置是否是合法的。
而後查找須要插入的位置的先後節點,循環插入。
public boolean addAll(Collection<? extends E> c) { return addAll(size, c); } public boolean addAll(int index, Collection<? extends E> c) { // 檢查添加位置 checkPositionIndex(index); // 將須要添加的集合轉換成爲數組 Object[] a = c.toArray(); // 獲取數組的大小 int numNew = a.length; // 若是數組長度爲0,說明沒有須要添加的元素,返回false if (numNew == 0) return false; // 插入位置的前節點和後續節點 Node<E> pred, succ; // 若是插入位置索引大小等於鏈表大小,那麼就是在最後插入元素 if (index == size) { // 最後插入元素沒有後續節點 succ = null; // 前一個節點就是以前的最後一個節點 pred = last; } else { // 查找到索引爲index 的節點 succ = node(index); // 獲取前一個節點 pred = succ.prev; } // 循環插入節點 for (Object o : a) { @SuppressWarnings("unchecked") E e = (E) o; // 初始化新節點,上一個節點是pred Node<E> newNode = new Node<>(pred, e, null); // 若是前一個節點是null,那麼第一個節點就是新的節點 if (pred == null) first = newNode; else // 不然pred的next置爲新節點 pred.next = newNode; pred = newNode; } // 若是插入位置沒有後續節點,也就是succ爲null if (succ == null) { // 最後一個節點也就是pred,剛剛插入的新節點 last = pred; } else { // 加入全部元素以後的最後一個節點的下一個節點指向succ(後續元素) pred.next = succ; // 插入位置的後續元素的上一個節點引用指向pred succ.prev = pred; } // 大小改變 size += numNew; // 修改次數增長 modCount++; return true; }
上面的代碼調用了node(index)
,這個在前面查找的時候已經說過了,再也不解釋。
在指定位置批量插入節點:
public boolean addAll(int index, Collection<? extends E> c) { // 檢查索引合法性 checkPositionIndex(index); // 將須要插入的集合轉換成爲數組 Object[] a = c.toArray(); // 數組的長度 int numNew = a.length; // 爲0則不須要插入 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; // 初始化節點,前置節點是插入位置的前節點,後續節點爲null Node<E> newNode = new Node<>(pred, e, null); // 若是插入位置前一個節點是null,說明插入位置是鏈表首 if (pred == null) // 首節點就是新插入的節點 first = newNode; else // 前節點的next指向新節點 pred.next = newNode; // 更新插入位置的前一個節點 pred = newNode; } // 若是插入位置的後一個節點爲空,說明插入位置是鏈表尾部 if (succ == null) { // 最後一個元素就是插入的元素 last = pred; } else { // 將插入的最後一個元素next指向succ pred.next = succ; // succ的上一個元素指向prev succ.prev = pred; } // 大小更新 size += numNew; // 修改次數改變 modCount++; // 返回成功 return true; }
將元素插入在指定位置,先判斷索引位置,若是索引位置是最後一個,那麼直接調用在最後添加元素函數便可,不然須要調用另一個函數,在某個元素前面插入:
public void add(int index, E element) { // index校驗 checkPositionIndex(index); // 索引等於鏈表大小 if (index == size) // 直接在最後插入元素 linkLast(element); else // 在某個節點前插入元素 linkBefore(element, node(index)); }
刪除第一個節點,先獲取首節點,判斷第一個節點是否是爲空,若是爲空則證實沒有該節點,拋出異常,內部調用的實際上是unlinkFirst()
。返回值是被移除的節點裏面的數值。
public E removeFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); } // 移除首節點 private E unlinkFirst(Node<E> f) { // assert f == first && f != null; // 獲取裏面的元素 final E element = f.item; // 保存下一個節點 final Node<E> next = f.next; // 將以前的首節點先後節點引用置空,有利於GC f.item = null; f.next = null; // help GC // 首節點更新 first = next; // 若是首節點是空的,那麼鏈表沒有元素了,最後一個節點天然也是null if (next == null) last = null; else // 不然當前的第一個節點的前置節點置null next.prev = null; // 鏈表大小-1 size--; // 修改次數增長 modCount++; return element; }
刪除最後一個節點,和上面的刪除首節點差很少,先取出最後一個節點,判斷是否爲空,若是爲空則拋出異常,不然會調用另外一個解除鏈接的函數unLinkLast()
。
public E removeLast() { final Node<E> l = last; if (l == null) throw new NoSuchElementException(); return unlinkLast(l); } private E unlinkLast(Node<E> l) { // assert l == last && l != null; // 保存被移除的節點的item final E element = l.item; // 獲取上一個節點 final Node<E> prev = l.prev; // 先後引用置空,有利於垃圾回收 l.item = null; l.prev = null; // help GC // 更新最後一個節點 last = prev; // 若是前置節點爲空,那麼鏈表已經沒有元素了 if (prev == null) first = null; else // 不然將上一個節點的next置null prev.next = null; // 大小該表 size--; // 修改次數增長 modCount++; // 返回被移除的節點的item值 return element; }
刪除某個元素分爲兩種狀況,元素爲null和非null,直接遍歷判斷,裏面真正刪除的方法實際上是unlink(E e)
,成功移除則返回true,注意這裏只會移除掉第一個,後續要是還有該節點,不會移除。
public boolean remove(Object o) { // 元素爲null if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) { unlink(x); return true; } } } else { // 元素不爲null for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) { // 移除節點 unlink(x); return true; } } } return false; }
unLink(E e)
方法以下:
E unlink(Node<E> x) { // assert x != null; // 保存被移除節點的item final E element = x.item; // 下一個節點 final Node<E> next = x.next; // 上一個節點 final Node<E> prev = x.prev; // 若是前置節點爲空,那麼首節點就是當前節點了 if (prev == null) { first = next; } else { // 前一個節點的next置爲下一個節點 prev.next = next; // 以前的節點的前一個節點置null x.prev = null; } // 若是next是空的,那麼上一個節點就是如今最後一個節點 if (next == null) { last = prev; } else { // next的上一個節點引用指向prev next.prev = prev; // 被刪除的元素的next置空 x.next = null; } // item置空 x.item = null; // 大小改變 size--; // 修改次數增長 modCount++; // 返回被刪除的節點裏面的item return element; }
移除裏面全部的元素:
public void clear() { // Clearing all of the links between nodes is "unnecessary", but: // - helps a generational GC if the discarded nodes inhabit // more than one generation // - is sure to free memory even if there is a reachable Iterator for (Node<E> x = first; x != null; ) { // 保存下一個 Node<E> next = x.next; // 當前元素置空 x.item = null; x.next = null; x.prev = null; x = next; } // 首節點和尾節點所有置null first = last = null; size = 0; modCount++; }
移除指定索引的元素。先經過索引找到節點,再移除指定的節點
public E remove(int index) { // 檢查合法性 checkElementIndex(index); // 先找到節點,再移除指定節點 return unlink(node(index)); }
更新指定索引的位置的元素,首先經過索引查找到該元素,而後修改item值,返回舊的item值。
public E set(int index, E element) { // 檢查索引是否合法 checkElementIndex(index); // 經過索引查找到節點 Node<E> x = node(index); // 保存舊的值 E oldVal = x.item; // 修改 x.item = element; // 返回舊的元素 return oldVal; }
由於LinkedList
也實現了queue
接口,因此它確定也實現了相關的方法,下面咱們看看:
獲取隊列第一個元素:
public E peek() { // 拿到第一個元素,final不可變 final Node<E> f = first; // 返回item值 return (f == null) ? null : f.item; }
也是獲取隊列第一個元素,裏面調用的是getFirst()
。
public E element() { return getFirst(); }
移除隊列第一個節點元素並返回,裏面調用的實際上是unlinkFirst()
public E poll() { // 獲取到第一個元素 final Node<E> f = first; // 移除並返回 return (f == null) ? null : unlinkFirst(f); }
移除隊列第一個元素,裏面調用的是removeFirst()
:
public E remove() { return removeFirst(); }
在隊列後面增長元素:
public boolean offer(E e) { return add(e); }
往隊列的前面插入元素,其實調用的是addFirst()
public boolean offerFirst(E e) { addFirst(e); return true; }
往隊列的後面添加元素,其實調用的是addList()
public boolean offerLast(E e) { addLast(e); return true; }
獲取第一個節點裏面的元素:
public E peekFirst() { final Node<E> f = first; return (f == null) ? null : f.item; }
獲取最後一個節點的元素:
public E peekLast() { final Node<E> l = last; return (l == null) ? null : l.item; }
獲取第一個元素,而且移除它,使用的是unlinkFirst(E e)
public E pollFirst() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); }
獲取隊列最後一個元素,而且移除它,調用的實際上是unlinkLast(E e)
public E pollLast() { final Node<E> l = last; return (l == null) ? null : unlinkLast(l); }
像是堆棧的特色,在前面添加元素:
public void push(E e) { addFirst(e); }
堆棧的特色,取出隊列首的第一個元素
public E pop() { return removeFirst(); }
移除元素,從前日後第一次出現的地方移除掉:
public boolean removeFirstOccurrence(Object o) { return remove(o); }
移除元素,最後一次出現的地方移除掉,和前面分析的同樣,分爲兩種狀況,null和非null。
public boolean removeLastOccurrence(Object o) { // 元素爲null if (o == null) { for (Node<E> x = last; x != null; x = x.prev) { if (x.item == null) { unlink(x); return true; } } } else { // 元素不是null for (Node<E> x = last; x != null; x = x.prev) { if (o.equals(x.item)) { unlink(x); return true; } } } return false; }
是否包含某個元素,其實調用的是indexOf()
方法,若是返回的索引不爲-1,則包含:
public boolean contains(Object o) { return indexOf(o) != -1; }
返回大小:
public int size() { return size; }
是否爲有效元素下標索引,從0到size-1
private boolean isElementIndex(int index) { return index >= 0 && index < size; }
是否爲有效位置索引,從0到size
private boolean isPositionIndex(int index) { return index >= 0 && index <= size; }
獲取指定索引位置的ListIterator
:
public ListIterator<E> listIterator(int index) { // 檢查合法性 checkPositionIndex(index); return new ListItr(index); }
獲取倒序的迭代器:
public Iterator<E> descendingIterator() { return new DescendingIterator(); }
拷貝克隆函數,一個是父類的克隆函數,另外一個是重寫的克隆函數,這裏比較特殊,由於LinkedList
是鏈表,自己只保存了第一個和最後一個的引用,因此拷貝的時候須要向裏面添加元素的方式進行拷貝。
public Object clone() { LinkedList<E> clone = superClone(); // Put clone into "virgin" state clone.first = clone.last = null; clone.size = 0; clone.modCount = 0; // 添加元素到拷貝的隊列中 for (Node<E> x = first; x != null; x = x.next) clone.add(x.item); return clone; } private LinkedList<E> superClone() { try { return (LinkedList<E>) super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(e); } }
轉換成爲數組,經過循環實現
public Object[] toArray() { Object[] result = new Object[size]; int i = 0; // 循環實現 for (Node<E> x = first; x != null; x = x.next) result[i++] = x.item; return result; }
轉換成爲指定類型的數組,和前面不一樣的是,這裏初始化的時候使用類型反射建立(T[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size)
public <T> T[] toArray(T[] a) { if (a.length < size) a = (T[])java.lang.reflect.Array.newInstance( a.getClass().getComponentType(), size); int i = 0; Object[] result = a; for (Node<E> x = first; x != null; x = x.next) result[i++] = x.item; if (a.length > size) a[size] = null; return a; }
獲取可分割迭代器:
public Spliterator<E> spliterator() { return new LLSpliterator<E>(this, -1, 0); }
裏面定義了三種迭代器,都是之內部類的方式實現,分別是:
先來講說ListItr
,這個迭代器主要是有next()
,hashNext()
,hasPrevious()
,previous()
,nextIndex()
,previousIndex()
,remove()
,set()
,add()
,forEachRemaining()
方法:
next()
:獲取下一個元素hashNext()
:是否有下一個元素hasPrevious()
:是否有上一個元素previous()
:上一個元素nextIndex()
:下一個索引位置previousIndex()
:上一個索引位置remove()
:刪除當前索引位置的元素set()
:更新元素add()
:新增元素forEachRemaining()
:遍歷剩下的元素裏面主要有集合重要的屬性:
lastReturned
:上一次返回的元素next
:下一個返回的元素nextIndex
:下一個索引expectedModCount
:期待修改的次數private class ListItr implements ListIterator<E> { // 上一個返回的元素 private Node<E> lastReturned; // 下一個元素 private Node<E> next; // 下一個索引 private int nextIndex; // 期待的修改次數 private int expectedModCount = modCount; // 初始化 ListItr(int index) { // 根據索引位置更新下一個返回的節點 next = (index == size) ? null : node(index); // 更新索引 nextIndex = index; } // 是否有下一個元素:索引是否小於size public boolean hasNext() { return nextIndex < size; } // 獲取下一個元素 public E next() { // 檢查修改合法化 checkForComodification(); // 若是沒有下一個元素會拋異常,因此使用前須要先判斷 if (!hasNext()) throw new NoSuchElementException(); // 上一次返回的元素更新 lastReturned = next; // 更新下一次返回的元素 next = next.next; // 更新索引 nextIndex++; // 返回item return lastReturned.item; } // 是否有上一個:下一個返回的元素索引是否是大於0 public boolean hasPrevious() { return nextIndex > 0; } // 返回上一個元素 public E previous() { // 檢查 checkForComodification(); // 判斷是否有上一個元素 if (!hasPrevious()) throw new NoSuchElementException(); // 上一個返回的元素,須要更新 lastReturned = next = (next == null) ? last : next.prev; // 更新索引 nextIndex--; return lastReturned.item; } // 下一個索引 public int nextIndex() { return nextIndex; } // 上一個索引 public int previousIndex() { return nextIndex - 1; } // 移除當前位置的索引 public void remove() { // 檢查修改合法性 checkForComodification(); if (lastReturned == null) throw new IllegalStateException(); // 獲取下一個元素 Node<E> lastNext = lastReturned.next; // 移除上一個返回的元素 unlink(lastReturned); // 若是下一個是上次返回的元素,那麼下一個元素須要更新,由於該元素已經被移除了 if (next == lastReturned) next = lastNext; else // 更新索引 nextIndex--; lastReturned = null; expectedModCount++; } // 更新 public void set(E e) { if (lastReturned == null) throw new IllegalStateException(); checkForComodification(); lastReturned.item = e; } public void add(E e) { checkForComodification(); lastReturned = null; // 若是下一個元素是空,那就是在隊尾添加元素 if (next == null) linkLast(e); else // 不然就是在next索引處添加元素 linkBefore(e, next); // 更新索引 nextIndex++; expectedModCount++; } // 遍歷剩下的元素 public void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); // 使用循環,索引不斷後移,遍歷 while (modCount == expectedModCount && nextIndex < size) { // 對每一個節點元素執行操做 action.accept(next.item); lastReturned = next; next = next.next; nextIndex++; } checkForComodification(); } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
上面的迭代器沒有什麼好說的,就是往前面和後面遍歷的功能,以及增刪改的功能。
這個迭代器有點意思,也很簡單,就是一個倒序的功能,功能實現也十分簡單:
倒序就是別人從前日後,它恰恰從後往前遍歷,emmmmmmm
private class DescendingIterator implements Iterator<E> { private final ListItr itr = new ListItr(size()); public boolean hasNext() { return itr.hasPrevious(); } public E next() { return itr.previous(); } public void remove() { itr.remove(); } }
這個迭代器有點東西,感受和其它的不太同樣,LLSpliterator
是在使用node的next進行迭代,下面分析一下:主要是爲了將元素分爲多份,而後再用多線程來處理。
值得注意的是:分割的時候,LinkedList
不是1/2分割,而是每一次分割出來的大小都是遞增的,遞增的大小是BATCH_UNIT
,可是返回的不是LLSpliterator
,而是ArraySpliterator
,每次都分割出更多的元素,轉成數組結構,這也許是出自於性能考慮,比較指針遍歷太慢了,我猜的的...別打我
static final class LLSpliterator<E> implements Spliterator<E> { // 分割長度增長單位 static final int BATCH_UNIT = 1 << 10; // batch array size increment // 最大分割長度 static final int MAX_BATCH = 1 << 25; // max batch array size; final LinkedList<E> list; // null OK unless traversed // 當前節點 Node<E> current; // current node; null until initialized // 大小估算 int est; // 期待修改的次數 int expectedModCount; // initialized when est set // 分割長度 int batch; // batch size for splits LLSpliterator(LinkedList<E> list, int est, int expectedModCount) { this.list = list; this.est = est; this.expectedModCount = expectedModCount; } final int getEst() { int s; // force initialization final LinkedList<E> lst; if ((s = est) < 0) { if ((lst = list) == null) s = est = 0; else { expectedModCount = lst.modCount; current = lst.first; s = est = lst.size; } } return s; } // 估算大小 public long estimateSize() { return (long) getEst(); } // 分割 public Spliterator<E> trySplit() { Node<E> p; // 獲取大小 int s = getEst(); // 當前節點不爲空 if (s > 1 && (p = current) != null) { // 分割位置結束:分割位置+分割單位 int n = batch + BATCH_UNIT; // 若是大於大小,就限制最後的位置 if (n > s) n = s; // 最大的分割位置 if (n > MAX_BATCH) n = MAX_BATCH; // 數組 Object[] a = new Object[n]; int j = 0; // 將當前位置到n的位置循環,存放到a數組中 do { a[j++] = p.item; } while ((p = p.next) != null && j < n); current = p; batch = j; est = s - j; // ArraySpliterator每次分割成一半一半,而IteratorSpliterator算術遞增 return Spliterators.spliterator(a, 0, j, Spliterator.ORDERED); } return null; } // 對剩下的元素進行處理 public void forEachRemaining(Consumer<? super E> action) { Node<E> p; int n; if (action == null) throw new NullPointerException(); if ((n = getEst()) > 0 && (p = current) != null) { current = null; est = 0; do { E e = p.item; p = p.next; action.accept(e); } while (p != null && --n > 0); } if (list.modCount != expectedModCount) throw new ConcurrentModificationException(); } // 對後面一個元素進行處理 public boolean tryAdvance(Consumer<? super E> action) { Node<E> p; if (action == null) throw new NullPointerException(); if (getEst() > 0 && (p = current) != null) { --est; E e = p.item; current = p.next; action.accept(e); if (list.modCount != expectedModCount) throw new ConcurrentModificationException(); return true; } return false; } public int characteristics() { return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED; } }
序列化和反序列化的時候,須要重寫,由於咱們保存的只有第一個和最後一個節點的引用,咱們序列化須要保存大小和引用,因此須要重寫,要不反序列化回來就找不到next
,節點之間的關係就會丟失。
序列化的時候以下,寫入了size
,以及遍歷的時候將節點的item
值寫入。
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { // Write out any hidden serialization magic s.defaultWriteObject(); // Write out size s.writeInt(size); // Write out all elements in the proper order. for (Node<E> x = first; x != null; x = x.next) s.writeObject(x.item); }
反序列化的時候,讀入大小size
以及每一個節點裏面的元素item
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // 默認序列化 s.defaultReadObject(); // 大小 int size = s.readInt(); // 按照順序讀入元素 for (int i = 0; i < size; i++) linkLast((E)s.readObject()); }
LinkedList
底層是用鏈表實現的,並且是雙向鏈表,而且實現了Queue
接口,能夠當成雙向隊列或者堆棧來使用。也正是由於是鏈表實現,因此刪除元素比較快,可是查找的時候相對較慢。固然,也沒有什麼擴容,除非就是內存不夠了。LinkedList
繼承了AbstractSequentialList
,AbstractSequentialList
實現了get
,set
,add
,remove
等方法。【做者簡介】:
秦懷,公衆號【秦懷雜貨店】做者,技術之路不在一時,山高水長,縱使緩慢,馳而不息。這個世界但願一切都很快,更快,可是我但願本身能走好每一步,寫好每一篇文章,期待和大家一塊兒交流。
此文章僅表明本身(本菜鳥)學習積累記錄,或者學習筆記,若有侵權,請聯繫做者覈實刪除。人無完人,文章也同樣,文筆稚嫩,在下不才,勿噴,若是有錯誤之處,還望指出,感激涕零~