LinkedList源码分析(基于Java8)
- LinkedList是一個實(shí)現(xiàn)了List接口和Deque接口的雙端鏈表
- 有關(guān)索引的操作可能從鏈表頭開始遍歷到鏈表尾部,也可能從尾部遍歷到鏈表頭部,這取決于看索引更靠近哪一端。
- LinkedList不是線程安全的,如果想使LinkedList變成線程安全的,可以使用如下方式:
iterator()和listIterator()返回的迭代器都遵循fail-fast機(jī)制。
從上圖可以看出LinkedList與ArrayList的不同之處
- ArrayList直接繼承自AbstractList
- LinkedList繼承自AbstractSequentialList,然后再繼承自AbstractList。另外還實(shí)現(xiàn)了Dequeu接口,雙端隊列。
內(nèi)部結(jié)構(gòu)
LinkedList內(nèi)部是一個雙端鏈表的結(jié)構(gòu)
LinkedList的結(jié)構(gòu)
從上圖可以看出,LinkedList內(nèi)部是一個雙端鏈表結(jié)構(gòu),有兩個變量,first指向鏈表頭部,last指向鏈表尾部。
成員變量
size表示當(dāng)前鏈表中的數(shù)據(jù)個數(shù)
下面是Node節(jié)點(diǎn)的定義,LinkedList的靜態(tài)內(nèi)部類
從Node的定義可以看出鏈表是一個雙端鏈表的結(jié)構(gòu)。
構(gòu)造方法
LinkedList有兩個構(gòu)造方法,一個用于構(gòu)造一個空的鏈表,一個用已有的集合創(chuàng)建鏈表
添加
因為LinkedList即實(shí)現(xiàn)了List接口,又實(shí)現(xiàn)了Deque,所以LinkedList既可以添加將元素添加到尾部,也可以將元素添加到指定索引位置,還可以添加添加整個集合;另外既可以在頭部添加,又可以在尾部添加。
分別從List接口和Deque接口介紹。
List接口的添加
add(E e)
add(E e)用于將元素添加到鏈表尾部,實(shí)現(xiàn)如下:
public boolean add(E e) {linkLast(e);return true;}void linkLast(E e) {final Node<E> l = last;//指向鏈表尾部final Node<E> newNode = new Node<>(l, e, null);//以尾部為前驅(qū)節(jié)點(diǎn)創(chuàng)建一個新節(jié)點(diǎn)last = newNode;//將鏈表尾部指向新節(jié)點(diǎn)if (l == null)//如果鏈表為空,那么該節(jié)點(diǎn)既是頭節(jié)點(diǎn)也是尾節(jié)點(diǎn)first = newNode;else//鏈表不為空,那么將該結(jié)點(diǎn)作為原鏈表尾部的后繼節(jié)點(diǎn)l.next = newNode;size++;//增加尺寸modCount++; }從上面代碼可以看到,就是一個鏈表尾部添加一個雙端節(jié)點(diǎn)的操作,但是需要注意對鏈表為空時頭節(jié)點(diǎn)的處理。
add(int index,E e)
add(int index,E e)用于在指定位置添加元素
1. 檢查index的范圍,否則拋出異常
2. 如果插入位置是鏈表尾部,那么調(diào)用linkLast方
3. 如果插入位置是鏈表中間,那么調(diào)用linkBefore
看一下linkBefore的實(shí)現(xiàn)
在看linkBefore之前,先看一下node(int index)方法,該方法返回指定位置的節(jié)點(diǎn)
node(int index)將根據(jù)index是靠近頭部還是尾部選擇不同的遍歷方向
一旦得到了指定索引位置的節(jié)點(diǎn),再看linkBefore()
linkBefore()方法在第二個參數(shù)節(jié)點(diǎn)前插入一個新節(jié)點(diǎn)
linkBefore#第一步
linkBefore#第二步
linkBefore#第三步
linkBefore主要分三步
1. 創(chuàng)建newNode節(jié)點(diǎn),將newNode的后繼指針指向succ,前驅(qū)指針指向pred
2. 將succ的前驅(qū)指針指向newNode
3. 根據(jù)pred是否為null,進(jìn)行不同操作。
- 如果pred為null,說明該節(jié)點(diǎn)插入在頭節(jié)點(diǎn)之前,要重置first頭節(jié)點(diǎn)
- 如果pred不為null,那么直接將pred的后繼指針指向newNode即可
addAll
addAll有兩個重載方法
- 一個參數(shù)的方法表示將集合元素添加到鏈表尾部
- 兩個參數(shù)的方法指定了開始插入的位置
1. 檢查index索引范圍
2. 得到集合數(shù)據(jù)
3. 得到插入位置的前驅(qū)和后繼節(jié)點(diǎn)
4. 遍歷數(shù)據(jù),將數(shù)據(jù)插入到指定位置
Deque接口的添加
addFirst(E e)
將元素添加到鏈表頭部
public void addFirst(E e) {linkFirst(e);}private void linkFirst(E e) {final Node<E> f = first;final Node<E> newNode = new Node<>(null, e, f);//新建節(jié)點(diǎn),以頭節(jié)點(diǎn)為后繼節(jié)點(diǎn)first = newNode;//如果鏈表為空,last節(jié)點(diǎn)也指向該節(jié)點(diǎn)if (f == null)last = newNode;//否則,將頭節(jié)點(diǎn)的前驅(qū)指針指向新節(jié)點(diǎn)elsef.prev = newNode;size++;modCount++;}在頭節(jié)點(diǎn)插入一個節(jié)點(diǎn)使新節(jié)點(diǎn)成為新節(jié)點(diǎn),但是和linkLast一樣需要注意當(dāng)鏈表為空時,對last節(jié)點(diǎn)的設(shè)置
addLast(E e)
將元素添加到鏈表尾部,與add()方法一樣
public void addLast(E e) {linkLast(e); }offer(E e)
將數(shù)據(jù)添加到鏈表尾部,其內(nèi)部調(diào)用了add(E e)方法
public boolean offer(E e) {return add(e); }offerFirst(E e)方法
將數(shù)據(jù)插入鏈表頭部,與addFirst的區(qū)別在于
- 該方法可以返回特定的返回值
- addFirst的返回值為void。
offerLast(E e)方法
offerLast()與addLast()的區(qū)別和offerFirst()和addFirst()的區(qū)別一樣
添加操作總結(jié)
LinkedList由于實(shí)現(xiàn)了List和Deque接口,所以有多種添加方法,總結(jié)一下
- 將數(shù)據(jù)插入到鏈表尾部
- boolean add(E e):
- void addLast(E e)
- boolean offerLast(E e)
- 將數(shù)據(jù)插入到鏈表頭部
- void addFirst(E e)
- boolean offerFirst(E e)
- 將數(shù)據(jù)插入到指定索引位置
- boolean add(int index,E e)
2檢索
2.1 根據(jù)位置取數(shù)據(jù)
2.1.1 get(int index)
獲取任意位置的,get(int index)方法根據(jù)指定索引返回數(shù)據(jù),如果索引越界,那么會拋出異常
/*** Returns the element at the specified position in this list.** @param index index of the element to return* @return the element at the specified position in this list* @throws IndexOutOfBoundsException {@inheritDoc}*/public E get(int index) {checkElementIndex(index);return node(index).item;}1.檢查index邊界,index>=0&&index
2.返回指定索引位置的元素
2.1.2 獲得位置為0的頭節(jié)點(diǎn)數(shù)據(jù)
LinkedList中有多種方法可以獲得頭節(jié)點(diǎn)的數(shù)據(jù),區(qū)別在于對鏈表為空時的處理,是拋異常還是返回null
主要方法有g(shù)etFirst()、element()、peek()、peekFirst()
其中g(shù)etFirst()和element()方法將會在鏈表為空時,拋出異常
從代碼可以看到,element()方法的內(nèi)部就是使用getFirst()實(shí)現(xiàn)的。它們會在鏈表為空時,拋NoSuchElementException
下面再看peek()和peekFirst()
當(dāng)鏈表為空時,peek()和peekFirst()方法返回null
2.1.3 獲得位置為size-1的尾節(jié)點(diǎn)數(shù)據(jù)
獲得尾節(jié)點(diǎn)數(shù)據(jù)的方法有
- getLast()
getLast()在鏈表為空時,會拋NoSuchElementException,
- peekLast()
只是會返回null
peekLast()
2.2 根據(jù)對象得到索引
- 第一個匹配的索引
從前往后遍歷 - 最后一個匹配的索引
從后往前遍歷
2.2.1 indexOf()
/*** 返回第一個匹配的索引* in this list, or -1 if this list does not contain the element.* More formally, returns the lowest index {@code i} such that* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,* or -1 if there is no such index.** @param o element to search for* @return the index of the first occurrence of the specified element in* this list, or -1 if this list does not contain the element*/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;}可以看到,LinkedList可包含null,遍歷方式都是從前往后,一旦匹配了,就返回索引
2.2.2 lastIndexOf()
返回最后一個匹配的索引,實(shí)現(xiàn)為從后往前遍歷
/*** Returns the index of the last occurrence of the specified element* in this list, or -1 if this list does not contain the element.* More formally, returns the highest index {@code i} such that* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,* or -1 if there is no such index.** @param o element to search for* @return the index of the last occurrence of the specified element in* this list, or -1 if this list does not contain the element*/public int lastIndexOf(Object o) {int index = size;if (o == null) {for (Node<E> x = last; x != null; x = x.prev) {index--;if (x.item == null)return index;}} else {for (Node<E> x = last; x != null; x = x.prev) {index--;if (o.equals(x.item))return index;}}return -1;}2.3 檢查是否包含某對象
contains(Object o)
檢查對象o是否存在于鏈表中
從代碼可以看到contains()方法調(diào)用了indexOf()方法,只要返回結(jié)果不是-1,那就說明該對象存在于鏈表中
2.4 檢索操作總結(jié)
檢索操作分為按照位置得到對象以及按照對象得到位置兩種方式,其中按照對象得到位置的方法有indexOf()和lastIndexOf();按照位置得到對象有如下方法:
- 根據(jù)任意位置得到數(shù)據(jù)的get(int index)方法,當(dāng)index越界會拋出異常
- 獲得頭節(jié)點(diǎn)數(shù)據(jù)
- getFirst()和element()方法在鏈表為空時會拋出NoSuchElementException
- peek()和peekFirst()方法在鏈表為空時會返回null
- 獲得尾節(jié)點(diǎn)數(shù)據(jù)
- getLast()在鏈表為空時會拋出NoSuchElementException
- peekLast()在鏈表為空時會返回null
3刪除
- 按照位置刪除
- 返回是否刪除成功的標(biāo)志
- 返回被刪除的元素
- 按照對象刪除
3.1 刪除指定對象
remove(Object o)
一次只刪除一個匹配的對象,如果刪除了匹配對象,返回true,否則false
由于LinkedList可以存儲null,所以對刪除對象以是否為null做區(qū)分
然后從鏈表頭開始遍歷,一旦匹配,就會調(diào)用unlink()方法將該節(jié)點(diǎn)從鏈表中移除
下面是unlink()
/*** Unlinks non-null node x.*/E unlink(Node<E> x) {// assert x != null;final E element = x.item;final Node<E> next = x.next;//得到后繼節(jié)點(diǎn)final Node<E> prev = x.prev;//得到前驅(qū)節(jié)點(diǎn)//刪除前驅(qū)指針if (prev == null) {first = next;如果刪除的節(jié)點(diǎn)是頭節(jié)點(diǎn),令頭節(jié)點(diǎn)指向該節(jié)點(diǎn)的后繼節(jié)點(diǎn)} else {prev.next = next;//將前驅(qū)節(jié)點(diǎn)的后繼節(jié)點(diǎn)指向后繼節(jié)點(diǎn)x.prev = null;}//刪除后繼指針if (next == null) {last = prev;//如果刪除的節(jié)點(diǎn)是尾節(jié)點(diǎn),令尾節(jié)點(diǎn)指向該節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)} else {next.prev = prev;x.next = null;}x.item = null;size--;modCount++;return element;} unlink第一步第一步:得到待刪除節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)和后繼節(jié)點(diǎn)
unlink第二步
第二步:刪除前驅(qū)節(jié)點(diǎn)
unlink第三步
第三步:刪除后繼節(jié)點(diǎn)
經(jīng)過三步,待刪除的結(jié)點(diǎn)就從鏈表中脫離了。需要注意的是刪除位置是頭節(jié)點(diǎn)或尾節(jié)點(diǎn)時候的處理,上面的示意圖沒有特別指出。
3.2 按照位置刪除對象
3.2.1 刪除任意位置的對象
- boolean remove(int index)
刪除任意位置的元素,刪除成功將返回true,否則false
1. 檢查index范圍,屬于[0,size)
2. 將索引出節(jié)點(diǎn)刪除
3.2.2 刪除頭節(jié)點(diǎn)的對象
- remove()、removeFirst()、pop()
在鏈表為空時將拋出NoSuchElementException
remove()和pop()內(nèi)部調(diào)用了removeFirst()
而removeFirst()在鏈表為空時將拋出NoSuchElementException
- poll()和,pollFirst()
在鏈表為空時將返回null
poll()和pollFirst()的實(shí)現(xiàn)代碼是相同的,在鏈表為空時將返回null
3.2.3 刪除尾節(jié)點(diǎn)的對象
- removeLast()
可以看到removeLast()在鏈表為空時將拋出NoSuchElementException
- pollLast()
可以看到pollLast()在鏈表為空時會返回null,而不是拋出異常
3.3 刪除操作總結(jié)
刪除操作由很多種方法
按照指定對象刪除
boolean remove(Object o),一次只會刪除一個匹配的對象
按照指定位置刪除
- 刪除任意位置的對象
E remove(int index),當(dāng)index越界時會拋出異常 - 刪除頭節(jié)點(diǎn)位置的對象
- 在鏈表為空時拋出異常
E remove()、E removeFirst()、E pop() - 在鏈表為空時返回null
E poll()、E pollFirst()
- 在鏈表為空時拋出異常
- 刪除尾節(jié)點(diǎn)位置的對象
- 在鏈表為空時拋出異常
E removeLast() - 在鏈表為空時返回null
E pollLast()
- 在鏈表為空時拋出異常
4迭代器
LinkedList的iterator()內(nèi)部調(diào)用了其listIterator()方法,所以可只分析listIterator()方法listIterator()提供了兩個重載方法。
iterator()方法和listIterator()方法的關(guān)系如下
public Iterator<E> iterator() {return listIterator();}public ListIterator<E> listIterator() {return listIterator(0);}public ListIterator<E> listIterator(int index) {checkPositionIndex(index);return new ListItr(index);}從上面可以看到三者的關(guān)系是iterator()——>listIterator(0)——>listIterator(int index)
最終都會調(diào)用listIterator(int index),其中參數(shù)表示迭代器開始的位置
ListIterator是一個可以指定任意位置開始迭代,并且有兩個遍歷方法
下面直接看ListItr
private class ListItr implements ListIterator<E> {private Node<E> lastReturned;private Node<E> next;private int nextIndex;private int expectedModCount = modCount;//保存當(dāng)前modCount,確保fail-fast機(jī)制ListItr(int index) {// assert isPositionIndex(index);next = (index == size) ? null : node(index);//得到當(dāng)前索引指向的next節(jié)點(diǎn)nextIndex = index;}public boolean hasNext() {return nextIndex < size;}//獲取下一個節(jié)點(diǎn)public E next() {checkForComodification();if (!hasNext())throw new NoSuchElementException();lastReturned = next;next = next.next;nextIndex++;return lastReturned.item;}public boolean hasPrevious() {return nextIndex > 0;}//獲取前一個節(jié)點(diǎn),將next節(jié)點(diǎn)向前移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;elsenextIndex--;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);elselinkBefore(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();}}構(gòu)造器中,得到了當(dāng)前位置的節(jié)點(diǎn),就是變量next
next()返回當(dāng)前節(jié)點(diǎn)的值并將next指向其后繼節(jié)點(diǎn)
previous()返回當(dāng)前節(jié)點(diǎn)的前一個節(jié)點(diǎn)的值并將next節(jié)點(diǎn)指向其前驅(qū)節(jié)點(diǎn)
由于Node是一個雙端節(jié)點(diǎn),所以這兒用了一個節(jié)點(diǎn)就可以實(shí)現(xiàn)從前向后迭代和從后向前迭代
另外在ListIterator初始時,exceptedModCount保存了當(dāng)前的modCount,如果在迭代期間,有操作改變了鏈表的底層結(jié)構(gòu),那么再操作迭代器的方法時將會拋出ConcurrentModificationException。
5 例子
由于LinkedList是一個實(shí)現(xiàn)了Deque的雙端隊列,所以LinkedList既可以當(dāng)做Queue,又可以當(dāng)做Stack,下面的例子將LinkedList成Stack
public class LinkedStack<E> {private LinkedList<E> linkedList;public LinkedStack() {linkedList = new LinkedList<E>();}//壓入數(shù)據(jù)public void push(E e) {linkedList.push(e);}//彈出數(shù)據(jù),在Stack為空時將拋出異常public E pop() {return linkedList.pop();}//檢索棧頂數(shù)據(jù),但是不刪除public E peek() {return linkedList.peek();}}在將LinkedList當(dāng)做Stack時,使用pop()、push()、peek()方法需要注意的是LinkedList內(nèi)部是將鏈表頭部當(dāng)做棧頂,鏈表尾部當(dāng)做棧底,也就意味著所有的壓入、攤?cè)氩僮鞫荚阪湵眍^部進(jìn)行
6總結(jié)
LinkedList是基于雙端鏈表的List,其內(nèi)部的實(shí)現(xiàn)源于對鏈表的操作
- 適用于頻繁增加、刪除的情況
- 該類不是線程安全的
- 由于LinkedList實(shí)現(xiàn)了Queue接口,所以LinkedList不止有隊列的接口,還有棧的接口,可以使用LinkedList作為隊列和棧的實(shí)現(xiàn)
總結(jié)
以上是生活随笔為你收集整理的LinkedList源码分析(基于Java8)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++ using namespace
- 下一篇: Java基础-面向对象第二特征之继承(I