日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

LinkedList源码阅分析

發布時間:2025/3/15 编程问答 14 豆豆
生活随笔 收集整理的這篇文章主要介紹了 LinkedList源码阅分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

LinkedList里面涉及到的一些操作,非常細致,以避免出現的空指針,理解后對于其優點與確定會有一個更加整體的認識吧。

繼承關系圖(對比ArrayList)

元素的存儲結構
在LinkedList中,每一個元素都是Node存儲,Node擁有一個存儲值的item與一個前驅prev和一個后繼next,如下:

// 典型的鏈表結構

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;} }



構造函數與成員變量
變量主要有3個:

transient int size = 0;//當前列表的元素個數 /*** Pointer to first node.* Invariant: (first == null && last == null) ||* ? ? ? ? ? ?(first.prev == null && first.item != null)*/ transient Node<E> first;// 第一個元素 /*** Pointer to last node.* Invariant: (first == null && last == null) ||* ? ? ? ? ? ?(last.next == null && last.item != null)*/ transient Node<E> last;// 最后一個元素



在LinkedList中的構造函數有兩個,一個是無參的,另一個是帶Collection參數的。

public LinkedList() {}//無參構造函數 public LinkedList(Collection<? extends E> c) {this();addAll(c);//將c中的元素都添加到此列表中 }



其添加的過程中,此時size = 0,如下:

public boolean addAll(Collection<? extends E> c) {return addAll(size, c);//此時 size == 0 }



如果index==size,則添加c中的元素到列表的尾部;否則,添加的第index個元素的前面;

public boolean addAll(int index, Collection<? extends E> c) {// 檢查位置是否合法 位置是[0,size],注意是閉區間 否則報異常checkPositionIndex(index);Object[] a = c.toArray();// 得到一個元素數組int numNew = a.length;// c中元素的數量if (numNew == 0)return false;// 沒有元素,添加失敗// 主要功能是找到第size個元素的前驅和后繼。得到此元素需要分情況討論。// 這段代碼是各種情況的總和,可能有一點點容易懵逼。Node<E> pred, succ;// 前驅與后繼if (index == size) {// 如果位置與當前的size相同succ = null;// 無后繼pred = last;// 前驅為last,即第size個元素(最后一個元素)} else {// 若與size不同,即index位于[0, size)之間succ = node(index);// 后繼為第index個元素pred = succ.prev;// 前驅為后繼的前驅}// 后文有詳細的圖片說明// 開始逐個插入for (Object o : a) {@SuppressWarnings("unchecked") E e = (E) o;// 新建一個以pred為前驅、null為后繼、值為e的節點Node<E> newNode = new Node<>(pred, e, null);if (pred == null)// 前驅為空,則此節點被當做列表的第一個節點first = newNode;else// 規避掉了NullPointerException,感覺又達到了目的,又實現了邏輯pred.next = newNode;// 不為空,則將前驅的后繼改成當前節點pred = newNode;// 將前驅改成當前節點,以便后續添加c中其它的元素}// 至此,c中元素已添加到鏈表上,但鏈表中從size開始的那些元素還沒有鏈接到列表上// 此時就需要利用到之前找出來的succ值,它是作為這個c的整體后繼if (succ == null) {// 如果后繼為空,說明無整體后繼last = pred;// c的最后一個元素應當作為列表的尾元素} else {// 有整體后繼pred.next = succ;// pred即c中的最后一個元素,其后繼指向succ,即整體后繼succ.prev = pred;// succ的前驅指向c中的最后一個元素}// 添加完畢,修改參數size += numNew;modCount++;return true; }



返回序號為index的元素節點??催@段代碼中的if語句,真的是佩服,這樣寫代碼,都可以這樣減少查找次數。

Node<E> node(int index) {// assert isElementIndex(index);// 這個地方很有意思。視其與中值得差距,覺得從前遍歷還是從后遍歷。if (index < (size >> 1)) {Node<E> x = first;// 循環index次 迭代到所需要的元素for (int i = 0; i < index; i++)x = x.next;return x;} else {Node<E> x = last;// 循環size-1-index次for (int i = size - 1; i > index; i--)x = x.prev;return x;} }



測試代碼以及驗證輸出如下:

public class Main {public static void main(String[] args) {List<String> list = new LinkedList<>(Arrays.asList("1", "2", "3"));System.out.println(list.toString());list.addAll(2, Arrays.asList("4", "5"));System.out.println(list.toString());list.addAll(0, Arrays.asList("6", "7"));System.out.println(list.toString());} } --- [1, 2, 3] [1, 2, 4, 5, 3] [6, 7, 1, 2, 4, 5, 3]



增加元素
對于向列表中添加元素,先看一組基本的添加操作,具體如下:

將e鏈接成列表的第一個元素
源代碼以及相應的分析如下:

private void linkFirst(E e) {final Node<E> f = first;// 前驅為空,值為e,后繼為ffinal Node<E> newNode = new Node<>(null, e, f);first = newNode;// first指向newNode// 此時的f有可能為nullif (f == null)// 若f為空,則表明列表中還沒有元素last = newNode;// last也應該指向newNodeelsef.prev = newNode;// 否則,前first的前驅指向newNodesize++;modCount++; }



其過程大致如下兩圖所示:
初始狀態:

后續狀態:
添加元素作為第一個元素時,所需要做的工作,有下列所述:
首先,獲取第一個節點,然后將該節點的前驅指向新添加的元素所在的節點;
接著,將新添加的節點的后繼指向前第一個節點;
最后,將first指向新添加的元素的節點。添加完畢。


將e鏈接為最后一個元素
源代碼以及相應的解釋如下:

void linkLast(E e) {final Node<E> l = last;// 找到最后一個節點// 前驅為前last,值為e,后繼為nullfinal Node<E> newNode = new Node<>(l, e, null);last = newNode;// last一定會指向此節點if (l == null)// 最后一個節點為空,說明列表中無元素first = newNode;// first同樣指向此節點elsel.next = newNode;// 否則,前last的后繼指向當前節點size++;modCount++; }



其操作過程與前述linkFirst()的過程類似,因此其替換后的示意圖如下:


將e鏈接到節點succ前
源代碼以及相應的解析如下:

void linkBefore(E e, Node<E> succ) {// assert succ != null;final Node<E> pred = succ.prev; // 找到succ的前驅// 前驅為pred,值為e,后繼為succfinal Node<E> newNode = new Node<>(pred, e, succ);// 將succ的前驅指向當前節點succ.prev = newNode;if (pred == null)// pred為空,說明此時succ為首節點first = newNode;// 指向當前節點elsepred.next = newNode;// 否則,將succ之前的前驅的后繼指向當前節點size++;modCount++; }



這個操作有點類似將上述的兩個操作整合到一起。其操作簡圖如下:


有了上述的分析,我們再來看一些添加的操作,這些操作基本上是做了一些邏輯判斷,然后再調用上述三個方法去實現添加功能,這里略過就好。

?public boolean add(E e) {linkLast(e);return true;}// 只有這個是有一點邏輯的public void add(int index, E element) {checkPositionIndex(index);if (index == size)// 為最后一個節點,當然是添加到最后一個~linkLast(element);elselinkBefore(element, node(index));}public void addFirst(E e) {linkFirst(e);}public void addLast(E e) {linkLast(e);}


刪除元素
刪除就是添加過程的逆過程。同樣,在分析我們使用的接口前,先分析幾個我們看不到的方法,如下:

刪除首節點 private E unlinkFirst(Node<E> f) {// assert f == first && f != null;別忽略這里的斷言final E element = f.item;// 取出首節點中的元素final Node<E> next = f.next;// 取出首節點中的后繼f.item = null;f.next = null; // help GCfirst = next;// first指向前first的后繼,也就是列表中的2號位if (next == null)// 如果此時2號位為空,那么列表中此時已無節點last = null;// last指向nullelsenext.prev = null;// 首節點無前驅size--;modCount++;return element;// 返回首節點保存的元素值 }



刪除尾節點
此處的操作與刪除首節點的操作類似。

private E unlinkLast(Node<E> l) { // assert l == last && l != null;別忽略這里的斷言 final E element = l.item;// 取出尾節點中的元素 final Node<E> prev = l.prev;// 取出尾節點中的后繼 l.item = null; l.prev = null; // help GC last = prev;// last指向前last的前驅,也就是列表中的倒數2號位 if (prev == null)// 如果此時倒數2號位為空,那么列表中已無節點first = null;// first指向null elseprev.next = null;// 尾節點無后繼 size--; modCount++; return element;// 返回尾節點保存的元素值 }



刪除某個非空節點
這個也類似添加元素時的第三個基本操作,與結合了上述兩個操作有點類似。

// x即為要刪除的節點 E unlink(Node<E> x) { // assert x != null; final E element = x.item;// 保存x的元素值 final Node<E> next = x.next;// 保存x的后繼 final Node<E> prev = x.prev;// 保存x的前驅if (prev == null) {// 前驅為null,說明x為首節點first = next;// first指向x的后繼 } else {prev.next = next;// x的前驅的后繼指向x的后繼,即略過了xx.prev = null;// x.prev已無用處,置空引用 }if (next == null) {// 后繼為null,說明x為尾節點last = prev;// last指向x的前驅 } else {next.prev = prev;// x的后繼的前驅指向x的前驅,即略過了xx.next = null;// x.next已無用處,置空引用 }x.item = null;// 引用置空 size--; modCount++; return element;// 返回所刪除的節點的元素值 }


有了上面的幾個函數作為支撐,我們再來看下面的幾個我們能用來刪除節點的方法,他們也基本上是在一些邏輯判斷的基礎之上,再調用上述的基本操作:

public E removeFirst() {final Node<E> f = first;if (f == null)throw new NoSuchElementException();return unlinkFirst(f); } public E removeLast() {final Node<E> l = last;if (l == null)throw new NoSuchElementException();return unlinkLast(l); } // 遍歷列表中所有的節點,找到相同的元素,然后刪除它 public boolean remove(Object o) {if (o == null) {for (Node<E> x = first; x != null; x = x.next) {if (x.item == null) {unlink(x);return true;}}} else {for (Node<E> x = first; x != null; x = x.next) {if (o.equals(x.item)) {unlink(x);return true;}}}return false; } public E remove(int index) {checkElementIndex(index);return unlink(node(index)); }



修改元素
通過遍歷,循環index次,獲取到相應的節點后,再通過節點來修改元素值。

public E set(int index, E element) {checkElementIndex(index);Node<E> x = node(index);// 獲取到需要修改元素的節點E oldVal = x.item;// 保存之前的值x.item = element;// 修改return oldVal;// 返回修改前的值}


?

查詢元素 通過位置,循環index次,獲取到節點,然后返回該節點中元素的值public E get(int index) {checkElementIndex(index);return node(index).item;// 獲取節點,并返回節點中的元素值 }


?

還有兩個獲取首尾節點的元素的方法:public E getFirst() {final Node<E> f = first;if (f == null)throw new NoSuchElementException();return f.item; } public E getLast() {final Node<E> l = last;if (l == null)throw new NoSuchElementException();return l.item; }


?

獲取元素位置 從0開始往后遍歷public int indexOf(Object o) {int index = 0;if (o == null) {// 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;// 未找到,返回-1 }


?

從size - 1開始遍歷?;静僮髋c上述操作類似,只是起始位置不同。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; }



額外的話
在上面的諸多函數中,有許多是需要進行位置判斷的。在源碼中,位置判斷有兩個函數,一個是下標,一個是位置??吹竭@兩個函數,確實是有一些感觸,這確實是需要比較強的總結能力以及仔細的觀察能力。

// 下標,保證數組訪問不越界。 private boolean isElementIndex(int index) {return index >= 0 && index < size; } // 位置 private boolean isPositionIndex(int index) {return index >= 0 && index <= size; }



后記
LinkedList還實現了Queue這個接口,在實現這些接口時,仍然是做一些邏輯處理,然后調用上面所描述的基本操作,如link()、unlink()之類的,因此不再分析。還有其中的關于序列化、Iterator這兩塊,與ArrayList的實現也是不盡相同的,故在此可參考ArrayList中的解析。
?

?

總結

以上是生活随笔為你收集整理的LinkedList源码阅分析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 少妇高清精品毛片在线视频 | 女人被男人躁得好爽免费视频 | 中文字幕精品一区二区精 | 欧美a级黄色 | 天天摸天天舔天天操 | www插插插无码免费视频网站 | 中文字幕免费在线播放 | 国内毛片毛片毛片毛片毛片 | 亚洲 国产 日韩 欧美 | 亚洲三区在线 | 国产精品女人久久久 | 秋霞免费av | 国产成人综合亚洲 | 在哪里看毛片 | 外国av在线 | 在线观看1区 | 天天都色 | 亚洲国产电影在线观看 | 美女黄色大片 | 欧美男人又粗又长又大 | 五月婷婷免费视频 | 熟女视频一区 | 五月婷婷激情五月 | 日韩专区在线 | 精品福利在线观看 | 91精品国产自产精品男人的天堂 | 欧美一区亚洲 | 2020国产精品 | 综合爱爱网 | 亚洲iv一区二区三区 | 欧美中文字幕在线 | 国产无套在线观看 | 中文字幕电影av | 91色漫| 欧美性生交大片免费看app麻豆 | 成人做爰www看视频软件 | 成人28深夜影院 | 国产精品嫩草影院av蜜臀 | 国产成人av一区二区三区不卡 | 99久久综合网 | 狠狠爱五月婷婷 | 日韩丰满少妇无码内射 | av网址观看| 国产精品一二三四五区 | 手机在线免费av | 欧美福利一区 | 97精品久久| 精品人妻少妇一区二区三区 | 亚洲国产伊人 | 欧美aa级 | 香蕉视频在线网站 | 亚洲精品视频免费 | 国产成人在线视频播放 | 久久精品国产亚洲av无码娇色 | 9i免费看片黄 | 久久久久久久久蜜桃 | 中文字幕免费看 | 国模福利视频 | 欧美色图网站 | 日韩欧美一级二级 | 国产精品嫩草久久久久 | 91精产品一区观看 | 一区二区国产精品 | 韩国美女一区 | 天堂а√在线中文在线 | 亚洲av无码一区二区三区人 | 黄色二级视频 | 给我看高清的视频在线观看 | 天堂网中文在线 | 麻豆影视大全 | 成年人在线观看视频 | 亚洲视频精品在线 | 欧美日韩小视频 | 久草视频精品在线 | 国产91绿帽单男绿奴 | 东方成人av在线 | 久久精品综合 | aaaaaav| 亚洲欧洲精品一区 | 国语对白一区二区三区 | 亚洲精品一区二区三区蜜桃 | 人人澡人人看 | 久草精品在线观看 | 伊人天堂网 | 精品人妻久久久久一区二区三区 | 亚洲综合色视频 | 欧美国产综合 | 女女h百合无遮涩涩漫画软件 | 黄色香蕉网站 | 日本成人一区二区 | 超碰在线最新 | 日本韩国免费观看 | 日韩欧美天堂 | 国产男女激情 | 黄色成年人网站 | 欧美污污视频 | 欧美一级黄色片在线观看 | 日本成人一级片 | 国产av无码专区亚洲av麻豆 |