国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

LinkedList & ArrayDeque源碼閱讀

這篇具有很好參考價值的文章主要介紹了LinkedList & ArrayDeque源碼閱讀。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。


本人的源碼閱讀主要聚焦于類的使用場景,一般只在java層面進行分析,沒有深入到一些native方法的實現(xiàn)。并且由于知識儲備不完整,很可能出現(xiàn)疏漏甚至是謬誤,歡迎指出共同學習

本文基于corretto-17.0.9源碼,參考本文時請打開相應的源碼對照,否則你會不知道我在說什么

LinkedList簡介

從功能上看,LinkedList同時實現(xiàn)了List接口和Deque接口,也就是說它既可以看作一個順序容器,又可以看作一個隊列(Queue),同時又可以看作一個棧(Stack)。這樣看來,LinkedList簡直就是個全能冠軍。當你需要使用?;蛘哧犃袝r,可以考慮使用LinkedList,一方面是因為Java官方已經(jīng)聲明不建議使用Stack類,更遺憾的是,Java里根本沒有一個叫做Queue的類(它是個接口名字)。關于?;蜿犃校F(xiàn)在的首選是ArrayDeque,它有著比LinkedList(當作?;蜿犃惺褂脮r)有著更好的性能。

從內部實現(xiàn)來看,LinkedList其實只是對鏈表的封裝,沒什么特別之處,下面代碼分析也會非常簡短。與 ArrayList 相比,LinkedList 的增加和刪除的操作效率更高,而查找和修改的操作效率較低,這些顯然也是鏈表和數(shù)組的區(qū)別。

LinkedList例子

例子就不看了,都是些一眼懂的API

LinkedList繼承結構

LinkedList & ArrayDeque源碼閱讀,java

LinkedList,作為一個順序列表繼承于AbstractSequentialList,AbstractSequentialList與AbstractList的區(qū)別是,前者一般是不可隨機訪問的列表,或者說隨機訪問效率很低,而后者可以高效地基于下標隨機訪問,LinkedList作為鏈表的封裝,眾所周知鏈表無法隨機訪問元素,因此繼承于AbstractSequentialList。

LinkedList作為List(Sequence),每個元素都對應著一個下標,鏈表頭指針(first)指向的節(jié)點下標為0,尾指針(last)指向的節(jié)點下標為size-1。

其中JCF相關的接口和抽象類不了解的話,可以在參考「博客園」JCF相關基礎類接口/抽象類源碼閱讀。

LinkedList代碼分析

成員變量

LinkedList的成員變量也很少,了解鏈表的話一眼懂:

// 元素個數(shù)
transient int size = 0;
// 鏈表頭指針
transient Node<E> first;
// 鏈表尾指針
transient Node<E> last;

// 鏈表節(jié)點類
private static class Node<E> {
  E item;
  LinkedList.Node<E> next;
  LinkedList.Node<E> prev;

  Node(LinkedList.Node<E> prev, E element, LinkedList.Node<E> next) {
    this.item = element;
    this.next = next;
    this.prev = prev;
  }
}

頭和尾指針指向的都是真實元素節(jié)點,沒有dummy node。注意到Node有next和prev指針,說明是LinkedList維護的是一個雙向鏈表。

方法

首先是構造函數(shù),沒啥好說,一個無參構造對應空列表,一個根據(jù)Collection接口規(guī)范的用其他集合來初始化本集合的構造函數(shù):

public LinkedList() {}

public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

下面看一下各個方法,一些鏈表操作或比較簡單的方法就不分析了,只用注釋說明功能,先看一下內部方法:

// 頭插新節(jié)點
private void linkFirst(E e);
// 尾插新節(jié)點
void linkLast(E e);
// 在succ節(jié)點前插入新節(jié)點
void linkBefore(E e, Node<E> succ);
// 刪除頭節(jié)點
private E unlinkFirst(Node<E> f);
// 刪除尾節(jié)點
private E unlinkLast(Node<E> l);
// 刪除節(jié)點
E unlink(Node<E> x);
// 獲取下標為index的節(jié)點
LinkedList.Node<E> node(int index) {
  if (index < (size >> 1)) { // 如果節(jié)點在鏈表前半部分,則從first開始往后遍歷
    LinkedList.Node<E> x = first;
    for (int i = 0; i < index; i++)
      x = x.next;
    return x;
  } else { // 否則從last開始往前遍歷
    LinkedList.Node<E> x = last;
    for (int i = size - 1; i > index; i--)
      x = x.prev;
    return x;
  }
}

再看一下公有方法(太簡單的公有方法略過)

// 按c的迭代器的順序插入鏈表,最先遍歷到的元素下標最小
public boolean addAll(int index, Collection<? extends E> c);
// 清空鏈表,不像ArrayList的置null,這個方法會刪除所有節(jié)點
public void clear();

至此,實現(xiàn)Deque的方法已經(jīng)介紹完。在JCF介紹一文中提到過繼承AbstractSequentialList的類要么重寫增刪改查,要么重新實現(xiàn)列表迭代器,LinkedList兩者都做了,增刪改查方法很簡單,略過,看一下迭代器:

private class ListItr implements ListIterator<E> {
  // 相當于AbstractList.Itr的lastRet下標對應的節(jié)點
  private Node<E> lastReturned;
  // 相當于AbstractList.Itr的cursor下標對應的節(jié)點
  private Node<E> next;
  // 相當于Abstrac
  private int nextIndex;
  // 期望的修改次數(shù),用于檢測并發(fā)修改
  private int expectedModCount = modCount;
}

可以發(fā)現(xiàn)LinkedList實現(xiàn)的這個ListIterator沒什么特別的,也還是直接對鏈表進行操作,與AbstractList.ListIterator其實很類似,就不講了。

java 1.6開始還提供了一個反向迭代器,一眼懂:

public Iterator<E> descendingIterator() {
  return new DescendingIterator();
}

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

ArrayDeque簡介

ArrayDeque是雙端隊列接口Deque的實現(xiàn)。出于性能上的考慮,如果你要用FIFO隊列,可以優(yōu)先考慮ArrayDeque而不是LinkedList;如果要使用棧,可以優(yōu)先考慮ArrayDeque而不是LinkedList。ArrayDeque除了個別方法外,基本攤還時間復雜度都是O(1)。這個類的迭代器也是fail-fast的(這個概念在ArrayList那篇中講過)。

ArrayDeque繼承結構

LinkedList & ArrayDeque源碼閱讀,java

根據(jù)繼承結構可以確定,ArrayDeque就是一個用數(shù)組實現(xiàn)的雙端隊列,并且由于是“雙端”,我們可以進一步推測出應該是用循環(huán)數(shù)組來實現(xiàn)的。

ArrayDeque代碼分析

按照慣例,先搞懂成員變量的含義:

public class ArrayDeque<E> extends AbstractCollection<E> implements Deque<E>, Cloneable, Serializable {
  // 實際存儲元素的數(shù)組,沒有存元素的話為null
  transient Object[] elements;
  // 循環(huán)數(shù)組的頭指針,指向頭元素
  transient int head;
  // 循環(huán)數(shù)組的尾指針,指向下一個要入隊的尾元素
  transient int tail;
}

注意沒有存元素的位置是null,因此ArrayDeque不能存值為null的元素,至于為什么,看完下面分析就知道了。

先復習一下循環(huán)數(shù)組,當調用addLast的時候,往尾部增加元素;當調用addFirst的時候往頭部增加元素,畫一張圖幫助理解,其中數(shù)字代表元素下標,藍色的代表該位置有元素,其他的不用多解釋了:

LinkedList & ArrayDeque源碼閱讀,java

要注意的是,tail永遠要指向null,意味著數(shù)組大小為n的話,最多只能存n-1個元素。否則如果存滿數(shù)組的話,最終tail與head相等,此時無法判斷是隊空還是隊滿。

ArrayDeque沒有設size變量保存元素個數(shù),因為可以用head和tail計算得到。但是循環(huán)數(shù)組中tail可能大于head也可能小于head,因此設計一個概念:循環(huán)距離。首先tail入隊時是遞增的,head入隊下標是遞減的,因此head和tail的循環(huán)距離就是(結合圖來理解):

  • 當tail>=head,為tail-head
  • 當tail<head,為tail-head+數(shù)組長度(相當于數(shù)組長度減去空白格子的個數(shù),得到藍色格子個數(shù))

因此size就等于循環(huán)距離。ok,關于循環(huán)數(shù)組的原理以及元素出入隊的細節(jié)都搞清楚了,那么由于循環(huán)數(shù)組是個數(shù)組(廢話),總會有容量不夠的時候,下一步就來分析數(shù)組的擴容。擴容的核心方法是grow:

// 最少要增加needed(ArrayList中的grow是最少增加到minCapacity)
private void grow(int needed) {
  final int oldCapacity = elements.length;
  int newCapacity;
  // 這一段是計算擴容后的長度,總體策略是:
  // 如果數(shù)組比較?。ㄐ∮?4)那么擴容到2倍,否則擴容到1.5倍
  int jump = (oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1);
  if (jump < needed
      || (newCapacity = (oldCapacity + jump)) - MAX_ARRAY_SIZE > 0)
    newCapacity = newCapacity(needed, jump);
  // 這一段是擴容
  final Object[] es = elements = Arrays.copyOf(elements, newCapacity);
  if (tail < head || (tail == head && es[head] != null)) {
    int newSpace = newCapacity - oldCapacity;
    System.arraycopy(es, head,
                     es, head + newSpace,
                     oldCapacity - head);
    for (int i = head, to = (head += newSpace); i < to; i++)
      es[i] = null;
  }
}

grow的最后一段代碼是為了處理tail<=head的情況,具體還是看圖,假設從之前那張圖中數(shù)組的最后一個狀態(tài)開始擴容,大小+3(實際上不一定需要擴容,擴容的話也不一定是+3,這里只是為了方便說明這段代碼的最后一個部分):

LinkedList & ArrayDeque源碼閱讀,java

由于tail < head的情況下,元素是物理不連續(xù)的(循環(huán)數(shù)組嘛),因此最后一段 if 將后面那段移動到新數(shù)組的尾部,保持循環(huán)數(shù)組中元素的連續(xù)性。

Deque和Collection的一些方法就不說了,都很簡單。挑一點看著相對需要分析的函數(shù)來看一下:

// 刪除從頭到尾的第i個元素
boolean delete(int i) {
  final Object[] es = elements;
  final int capacity = es.length;
  final int h, t;
  // 計算第i個元素與頭的循環(huán)距離
  final int front = sub(i, h = head, capacity);
  // 計算第i個元素與尾的循環(huán)距離
  final int back = sub(t = tail, i, capacity) - 1;
  // 哪個循環(huán)距離小,就移動哪部分。這里意思是,因為是循環(huán)數(shù)組,刪除一個元素之后,可以把前半部分的元素整體往后移動,也可以把后半部分的元素整體往前移動,因此選擇需要移動元素少的那部分。
  if (front < back) {
    if (h <= i) {
      System.arraycopy(es, h, es, h + 1, front);
    } else { // Wrap around
      System.arraycopy(es, 0, es, 1, i);
      es[0] = es[capacity - 1];
      System.arraycopy(es, h, es, h + 1, front - (i + 1));
    }
    es[h] = null;
    head = inc(h, capacity);
    return false;
  } else {
    tail = dec(t, capacity);
    if (i <= tail) {
      System.arraycopy(es, i + 1, es, i, back);
    } else {
      System.arraycopy(es, i + 1, es, i, capacity - (i + 1));
      es[capacity - 1] = es[0];
      System.arraycopy(es, 1, es, 0, t - 1);
    }
    es[tail] = null;
    return true;
  }
}

還有的函數(shù)比如bulkRemoveModified,其實跟ArrayList的removeIf很像了,也是先標記所有將要被刪除的元素,然后再進行逐個刪除,這種做法是為了filter進行逐元素進行判斷時,保持遍歷過程中列表是可重入讀的,在實際進行刪除時使用雙指針法保持元素連續(xù)性。具體可以參考「博客園」ArrayList源碼閱讀。

最后看一眼迭代器吧,因為JCF前幾篇都已經(jīng)詳細介紹過迭代器,ArrayDeque的迭代器實現(xiàn)也沒什么特別的,就不展開講了:

private class DeqIterator implements Iterator<E> {
  int cursor;
  int remaining = size();
  int lastRet = -1;
}

總結

類似ArrayList是對數(shù)組的封裝,LinkedList是對數(shù)組的封裝,代碼看起來也比較簡單,適合編程新手學習一下“封裝”的概念。(又水一篇)。

java 1.6新出了一個ArrayDeque,出于性能上的考慮,如果你要用FIFO隊列,可以優(yōu)先考慮ArrayDeque而不是LinkedList;如果要使用棧,可以優(yōu)先考慮ArrayDeque而不是LinkedList。

參考鏈接

「博客園」JCF相關基礎類接口/抽象類源碼閱讀

「博客園」ArrayList源碼閱讀

「Java全棧知識體系」Collection - LinkedList源碼解析文章來源地址http://www.zghlxwxcb.cn/news/detail-793864.html

到了這里,關于LinkedList & ArrayDeque源碼閱讀的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • Java進階(7)——手動實現(xiàn)LinkedList & 內部node類的實現(xiàn) & 增刪改查的實現(xiàn) & toString方法 & 源碼的初步理解

    Java進階(7)——手動實現(xiàn)LinkedList & 內部node類的實現(xiàn) & 增刪改查的實現(xiàn) & toString方法 & 源碼的初步理解

    1.linkedList的節(jié)點,當前,上一個,下一個的思想; 2.根據(jù)index找node的方法,根據(jù)index確定從頭部還是尾部; 3.linkedlist的增刪改查的實現(xiàn),本質是改變節(jié)點的信息; 4.遞歸方法實現(xiàn)自定義鏈表的toString方法; Java進階(3)——手動實現(xiàn)ArrayList 源碼的初步理解分析 數(shù)組插入數(shù)據(jù)和

    2024年02月11日
    瀏覽(28)
  • 源碼分析——LinkedList源碼分析

    源碼分析——LinkedList源碼分析

    LinkedList 是一個實現(xiàn)了 List接口 和 Deque接口 的 雙端鏈表 。 LinkedList底層的鏈表結構使它 支持高效的插入和刪除操作 ,另外它實現(xiàn)了Deque接口,使得LinkedList類也具有隊列的特性; LinkedList 不是線程安全的 ,如果想使LinkedList變成線程安全的,可以調用靜態(tài)類 Collections類 中的 s

    2024年02月14日
    瀏覽(22)
  • LinkedList部分底層源碼分析

    LinkedList部分底層源碼分析

    JDK版本為1.8.0_271,以插入和刪除元素為例,LinkedList部分源碼如下: 插入刪除結點的過程如圖所示: 只有1個元素的LinkedList 包含4個元素的LinkedList add(E e)方法 add(int index,E e)方法 remove(Object obj)方法 remove(int index)方法

    2024年04月13日
    瀏覽(26)
  • Java數(shù)據(jù)結構學習和源碼閱讀(線性數(shù)據(jù)結構)

    Java數(shù)據(jù)結構學習和源碼閱讀(線性數(shù)據(jù)結構)

    鏈表的數(shù)據(jù)結構 一組由節(jié)點組成的數(shù)據(jù)結構,每個元素指向下一個元素,是線性序列。 最簡單的鏈表結構: 數(shù)據(jù) 指針(存放執(zhí)行下一個節(jié)點的指針) 不適合的場景: 需要循環(huán)遍歷將導致時間復雜度的提升 鏈表分類—單向鏈表 鏈表結構: 數(shù)據(jù) 指針 Next(指向下一個節(jié)點)

    2024年02月12日
    瀏覽(25)
  • 【數(shù)據(jù)結構與算法】4、雙向鏈表(學習 jdk 的 LinkedList 部分源碼)

    【數(shù)據(jù)結構與算法】4、雙向鏈表(學習 jdk 的 LinkedList 部分源碼)

    ?? 單鏈表的節(jié)點中只有一個 next 指針引用著下一個節(jié)點的地址 ?? 當要獲取單鏈表中的最后一個元素的時候,需要從頭節(jié)點開始遍歷到最后 ?? 單鏈表一開始的時候有 first 頭指針引用著頭節(jié)點的地址 ?? 雙向鏈表可以提升鏈表的綜合性能 ?? 雙向鏈表的節(jié)點中有 prev 指針引

    2024年02月12日
    瀏覽(22)
  • dubbo源碼閱讀之-java spi, dubbo spi 和 Spring spi 到底有啥區(qū)別

    dubbo源碼閱讀之-java spi, dubbo spi 和 Spring spi 到底有啥區(qū)別

    SPI 全稱為 Service Provider Interface,是一種服務發(fā)現(xiàn)機制。SPI 的本質是將接口實現(xiàn)類的全限定名配置在文件中,并由服務加載器讀取配置文件,加載實現(xiàn)類。這樣可以在運行時,動態(tài)為接口替換實現(xiàn)類。正因此特性,我們可以很容易的通過 SPI 機制為我們的程序提供拓展功能。

    2024年02月08日
    瀏覽(17)
  • 《手把手教你》系列技巧篇(六)-java+ selenium自動化測試-閱讀selenium源碼(詳細教程)

    《手把手教你》系列技巧篇(六)-java+ selenium自動化測試-閱讀selenium源碼(詳細教程)

    1.簡介 前面幾篇基礎系列文章,足夠你邁進了Selenium門檻,再不濟你也至少知道如何寫你第一個基于Java的Selenium自動化測試腳本。接下來宏哥介紹Selenium技巧篇,主要是介紹一些常用的Selenium方法或者接口(API),通過這些接口(API)或者方法的具體操作,達到能夠熟練使用

    2024年02月03日
    瀏覽(19)
  • 【Java】LinkedList 集合

    【Java】LinkedList 集合

    LinkedList集合特點 LinkedList 底層基于雙向鏈表實現(xiàn)增刪 效率非常高,查詢效率非常低。 LinkedList源碼解讀分析 LinkedList 是雙向鏈表實現(xiàn)的 List LinkedList 是非線程安全的(線程是不安全的) LinkedList 元素允許為null,允許重復元素 LinkedList 是基于鏈表是實現(xiàn)的,因此插入刪除效率高

    2024年02月07日
    瀏覽(22)
  • 【Java LinkedList詳解】

    Java中的LinkedList是一個重要的集合類,它是基于雙向鏈表實現(xiàn)的,具有動態(tài)擴容能力、查詢性能低、插入和刪除性能高、訪問和刪除任意位置元素性能高以及線程安全等特點。本文將詳細講解LinkedList的實現(xiàn)原理、特性和使用場景。 一、數(shù)據(jù)結構 LinkedList是基于雙向鏈表實現(xiàn)的

    2024年02月08日
    瀏覽(19)
  • 詳解Java LinkedList

    詳解Java LinkedList

    LinkedList是List接口的實現(xiàn)類,基于雙向鏈表實現(xiàn),繼承自AbstractSequentialList類,同時也實現(xiàn)了Cloneable、Serializable接口。此外還實現(xiàn)了Queue和Deque接口,可以作為隊列或雙端隊列使用。 LinkedList的插入刪除時間復雜度: 在頭部或尾部插入刪除元素,只需要修改頭節(jié)點或尾節(jié)點的指針

    2024年02月06日
    瀏覽(17)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包