主頁 > 後端開發 > JDK原始碼之LinkedList

JDK原始碼之LinkedList

2020-09-12 12:03:45 後端開發

下文帶/**/為原始碼注釋//為個人注釋源代碼使用這個顏色

Node物件存盤元素和元素左右資料的指標
add(e),我將我自己添加到上一個元素的Next

addFirst(e)替換鏈表頭
addLast(e)替換鏈表尾
addadd(index,ele)---linkLast or linkBefore

getFirst();getLast();效率最高的兩個獲取
為什么說鏈表獲取元素慢!
set(index,ele)更新元素同樣需要遍歷集合!
removeFirst洗掉并重新賦值first
removeLast洗掉并重新賦值Last
remove(index)四種洗掉情況!
remove(obj)洗掉指定元素的兩種情況

Node物件存盤元素和元素左右資料的指標

/*構建一個空串列*/

//無參構造器

public LinkedList() {}

//將要添加的元素放到Node物件中的item屬性中,

//多個Node物件構成一個鏈表,物件中持有下一個

//和上一個Node物件

private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {

//當前Node物件
this.item = element;

//下一個
this.next = next;

//上一個
this.prev = prev;
}
}

/**指向最后一個節點的指標**/

transient Node<E> last;//LinkedList的成員變數

/*指向第一個節點的指標*/

transient Node<E> first;//LinkedList的成員變數

/*該串列被修改的次數*/

protected transient int modCount = 0;//LinkedList的成員變數

//當前集合的元素總個數

transient int size = 0;//LinkedList的成員變數

add(e),我將我自己添加到上一個元素的Next

/*將指定的元素追加到串列的末尾,*/

//添加元素

public boolean add(E e) {
linkLast(e);
return true;
}

/*e作為最后一個元素,*/

void linkLast(E e) {

//先拿到串列中最后一個元素,串列是第一次添加元素是沒有的
final Node<E> l = last;

//創建Node物件,將最后一個元素當成Node的prev

//傳入的元素是當前Node元素,下一個不傳
final Node<E> newNode = new Node<>(l, e, null);

//當前創建的Node成為最后一個元素,也就是下一個Node的prev
last = newNode;

//如果上一個為null,當鏈表是第一次添加元素這個是null
if (l == null)

//將新增的Node賦值到第一個節點的指向,現在來看這個值只會被賦值一次?
first = newNode;
else

//如果last不是null則表示串列內已經有元素

//一個新增加的元素其實是一個Node但是這個Node是沒有next的

//這個賦值代表著,如果鏈表內有元素,將這個新創建的Node賦值

//給上一個Node的next
l.next = newNode;

size++;
modCount++;
}

addFirst(e)替換鏈表頭

/*在串列的開頭插入指定的元素,*/

public void addFirst(E e) {
linkFirst(e);
}

/*e作為第一個元素,*/

private void linkFirst(E e) {

//拿到first元素
final Node<E> f = first;

//創建新Node它的first是null,next是原來的first
final Node<E> newNode = new Node<>(null, e, f);

//將first指標指向新Node
first = newNode;

//如果f是null表示之前沒有元素
if (f == null)

//則新添加的node同時也是last
last = newNode;
else//如果f不是null

//將新添加的Node指標賦值給原有first的prev

f.prev = newNode;

//計數器
size++;
modCount++;
}

addLast(e)替換鏈表尾

/*將指定的元素追加到串列的末尾,*/

public void addLast(E e) {
linkLast(e);
}

/*e作為最后一個元素,*/

void linkLast(E e) {

//拿到原來的last
final Node<E> l = last;

//創建新的Node它的prev是原來的last阿德next是null
final Node<E> newNode = new Node<>(l, e, null);

//將新創建的Node賦值給last
last = newNode;

//如果l==null表示原來沒有元素
if (l == null)

//此時只有新增加的元素,所以它也是first
first = newNode;
else

//如果原來有元素,則原來的last的next為新創建的Node
l.next = newNode;

//計數器
size++;
modCount++;
}

addadd(index,ele)---linkLast or linkBefore

/*

在串列的指定位置插入指定的元素,將元素當前的位置

(如果有的話)和后續的元素向右移動(在它們的索引中添加一個),

*/

public void add(int index, E element) {

//index只能大于=0或者小于=size,否則例外

checkPositionIndex(index);

if (index == size)

//如果index等于size表示當前做的是追加操作
linkLast(element);
else

//那這里應該是包含0的情況其實就是linkFirst()

//不清楚為啥不判斷下,

//首先獲取下標Node
linkBefore(element, node(index));
}

private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

/*告知引數是否是迭代器或添加操作的有效位置的索引,*/

private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}

/*將元素e插入到非空節點succ之前,*/

void linkBefore(E e, Node<E> succ) {
// assert succ != null;

//拿到下標Node的prev

final Node<E> pred = succ.prev;

//創建新的Node它的prev是succ Node的prev,它的next是succ

//簡單描述新創建的元素在index的前邊
final Node<E> newNode = new Node<>(pred, e, succ);

//index Node的prev指標變成了新創建的Node
succ.prev = newNode;

//原來的Node的prev如果是null表示它原來是第一個元素

//那就把新創建的Node賦值到first
if (pred == null)
first = newNode;

//原來的Node不是first則將原有Node的prev的next賦值成newNode
else
pred.next = newNode;

//計數器
size++;
modCount++;
}

getFirst();getLast();效率最高的兩個獲取

//第一個Node創建后,last和first都是Node1,第二個Node創建后last是Node2,還是firstNode1,

//拿到Node后獲取成員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;

}

為什么說鏈表獲取元素慢!

/*回傳串列中指定位置的元素,*/

public E get(int index) {

//判斷index是否合法

checkElementIndex(index);

//這是個重點方法,

return node(index).item;

}

private void checkElementIndex(int index) {

if (!isElementIndex(index))

 throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

}

/**告知引數是否為現有元素的索引,**/

private boolean isElementIndex(int index) {

//要查詢的index在0和size-1之間則回傳true

return index >= 0 && index < size;

}

/*回傳指定元素索引處的(非空)節點,*/

Node<E> node(int index) {

//如果要查詢的index小于當前size/2

if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {//如果要查詢的index大于當前size/2
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}

//這里假設LinkedList元素總個數為100,當前查詢的index為49則100/2=50

//49<50,先獲取集合內第一個Node,然后從0開始,每次都取Node的Next

//也就是Next代表的Node,需要回圈49次才能拿到index為49的元素,

//如果當前元素為100,當前查詢的index為50,則從99開始,往后回圈查詢

//需要回圈48次,最快的兩種情況是要查詢第0號下標則第一個回圈不滿足直接

//回傳first或者要查詢99號下標不滿足第二個回圈直接回傳last,但是這兩

//中情況使用getFirst()或者getLast()是最快的,

set(index,ele)更新元素同樣需要遍歷集合!

/*將串列中指定位置的元素替換為指定元素,*/

public E set(int index, E element) {

//檢驗下標是否合格,上邊說過這個方法,
checkElementIndex(index);

//同樣需要找到對應的Node,上邊說過這個方法,
Node<E> x = node(index);

//獲取老的元素
E oldVal = x.item;

//新的元素替換老的元素
x.item = element;

//回傳被替換的元素
return oldVal;
}

removeFirst洗掉并重新賦值first

/*洗掉并回傳串列中的第一個元素,*/

public E removeFirst() {

//獲取first元素
final Node<E> f = first;
if (f == null)

//如果是null表示當前集合沒有元素,拋出例外
throw new NoSuchElementException();
return unlinkFirst(f);
}

/*斷開第一個非空節點的鏈接*/

private E unlinkFirst(Node<E> f) {

//獲取第一個Node的item
final E element = f.item;

//獲取第一個Node的Next
final Node<E> next = f.next;

//將item置為null
f.item = null;

//將next置為null,因為first是沒有prev的,所以將

//Node的ele元素和next全部置為null則代表可以

//進行垃圾回收
f.next = null; // help GC

//第一個Node被移除后它next的理應的是first
first = next;

//如果被移除的Node的next現在是first是null則表示

//當前集合沒有元素則last也應當是null
if (next == null)
last = null;

//如果first被移除后集合內還有元素,則新的first的prev是null
else
next.prev = null;

//計數器操作

size--;
modCount++;

//回傳被移除的元素
return element;
}

removeLast洗掉并重新賦值Last

/*洗掉并回傳串列中的最后一個元素,*/

public E removeLast() {

//獲取最后一個Node
final Node<E> l = last;

//如果是null拋例外
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}

/*解除最后一個非空節點l的鏈接,*/

private E unlinkLast(Node<E> l) {

//拿到last的元素
final E element = l.item;

//拿到last的上一個
final Node<E> prev = l.prev;

//斷掉指標
l.item = null;

//斷掉指標,等待被回收
l.prev = null; // help GC

//last被洗掉后它的prev理應的是新的last
last = prev;

//如果新的last是null,則表示集合內沒有元素

//first置為null
if (prev == null)
first = null;
else

//順利成為last后它失去next的指標,也就是oldNext的指標
prev.next = null;

//計數器
size--;
modCount++;

//回傳被洗掉的元素
return element;
}

remove(index)四種洗掉情況!

/*

移除此串列中指定位置的元素,將后面的元素向左移動

(從它們的索引中減去1),回傳從串列中洗掉的元素,

*/

public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}

/*Unlinks non-null node x.*/

E unlink(Node<E> x) {
// assert x != null;

//需要被洗掉Node的元素內容
final E element = x.item;

//需要被洗掉Node的下一個
final Node<E> next = x.next;

//需要被洗掉Node的上一個
final Node<E> prev = x.prev;

//如果上一個等于null表示被洗掉的元素是第一個

if (prev == null) {

//則需要將被洗掉的Node的next賦值到first
first = next;
} else {

//如果被洗掉的Node不是第一個,需要將它的next

prev.next = next;

//賦值給它的prev的next,并且將它的上一個指標斷掉,
x.prev = null;
}

//如果被洗掉的Node的next是null表示它是最后一個

if (next == null) {

//則需要將它的prev賦值到last
last = prev;
} else {

//否則表示它不是最后一個,需要將被洗掉的Node的prev

//轉移到下一個Node的prev
next.prev = prev;

//將next指標斷掉
x.next = null;
}

//將元素內容指標斷掉

x.item = null;

//計數器操作
size--;
modCount++;
return element;
}

remove(obj)洗掉指定元素的兩種情況

/*如果指定元素出現,則從串列中洗掉第一個出現的元素,如果此串列不包含該元素,則將對其進行更改,*/

//其實這個注釋是非常不令人理解的,,,我的理解是不包含該元素則不對其進行更改

public boolean remove(Object o) {

//如果被洗掉的是null
if (o == null) {

//從頭遍歷集合,根據每一個Node的item判斷是不是null

//回圈結束條件是next==null

for (Node<E> x = first; x != null; x = x.next) {

//如果找到了就洗掉還是執行的unlink()最后回傳true
if (x.item == null) {
unlink(x);
return true;
}
}
} else {

//如果不是null照樣也是從頭遍歷集合呼叫被洗掉元素的equals

//這意味著如果你被洗掉的元素和集合內其他元素的型別不同則

//可能會報錯
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {

//如果相同則洗掉并且回傳true
unlink(x);
return true;
}
}
}

//最后沒有滿足條件的回傳false洗掉失敗
return false;
}

 

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/13922.html

標籤:Java

上一篇:2020年高頻Java面試題集錦(含答案),讓你的面試之路暢通無阻!

下一篇:程式員你是怎么繪制架構圖?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more