死磕 java集合之ArrayDeque源碼分析

来源:https://www.cnblogs.com/tong-yuan/archive/2019/04/29/ArrayDeque.html
-Advertisement-
Play Games

什麼是雙端隊列? ArrayDeque是怎麼實現雙端隊列的? ArrayDeque是線程安全的嗎? ArrayDeque是有界的嗎? ...


問題

(1)什麼是雙端隊列?

(2)ArrayDeque是怎麼實現雙端隊列的?

(3)ArrayDeque是線程安全的嗎?

(4)ArrayDeque是有界的嗎?

簡介

雙端隊列是一種特殊的隊列,它的兩端都可以進出元素,故而得名雙端隊列。

ArrayDeque是一種以數組方式實現的雙端隊列,它是非線程安全的。

繼承體系

qrcode

通過繼承體系可以看,ArrayDeque實現了Deque介面,Deque介面繼承自Queue介面,它是對Queue的一種增強。


public interface Deque<E> extends Queue<E> {
    // 添加元素到隊列頭
    void addFirst(E e);
    // 添加元素到隊列尾
    void addLast(E e);
    // 添加元素到隊列頭
    boolean offerFirst(E e);
    // 添加元素到隊列尾
    boolean offerLast(E e);
    // 從隊列頭移除元素
    E removeFirst();
    // 從隊列尾移除元素
    E removeLast();
    // 從隊列頭移除元素
    E pollFirst();
    // 從隊列尾移除元素
    E pollLast();
    // 查看隊列頭元素
    E getFirst();
    // 查看隊列尾元素
    E getLast();
    // 查看隊列頭元素
    E peekFirst();
    // 查看隊列尾元素
    E peekLast();
    // 從隊列頭向後遍歷移除指定元素
    boolean removeFirstOccurrence(Object o);
    // 從隊列尾向前遍歷移除指定元素
    boolean removeLastOccurrence(Object o);

    // *** 隊列中的方法 ***
    
    // 添加元素,等於addLast(e)
    boolean add(E e);
     // 添加元素,等於offerLast(e)
    boolean offer(E e);
    // 移除元素,等於removeFirst()
    E remove();
    // 移除元素,等於pollFirst()
    E poll();
    // 查看元素,等於getFirst()
    E element();
    // 查看元素,等於peekFirst()
    E peek();

    // *** 棧方法 ***

    // 入棧,等於addFirst(e)
    void push(E e);
    // 出棧,等於removeFirst()
    E pop();

    // *** Collection中的方法 ***
    
    // 刪除指定元素,等於removeFirstOccurrence(o)
    boolean remove(Object o);
    // 檢查是否包含某個元素
    boolean contains(Object o);
    // 元素個數
    public int size();
    // 迭代器
    Iterator<E> iterator();
    // 反向迭代器
    Iterator<E> descendingIterator();
}

Deque中新增了以下幾類方法:

(1)*First,表示從隊列頭操作元素;

(2)*Last,表示從隊列尾操作元素;

(3)push(e),pop(),以棧的方式操作元素的方法;

源碼分析

主要屬性

// 存儲元素的數組
transient Object[] elements; // non-private to simplify nested class access
// 隊列頭位置
transient int head;
// 隊列尾位置
transient int tail;
// 最小初始容量
private static final int MIN_INITIAL_CAPACITY = 8;

從屬性我們可以看到,ArrayDeque使用數組存儲元素,並使用頭尾指針標識隊列的頭和尾,其最小容量是8。

主要構造方法

// 預設構造方法,初始容量為16
public ArrayDeque() {
    elements = new Object[16];
}
// 指定元素個數初始化
public ArrayDeque(int numElements) {
    allocateElements(numElements);
}
// 將集合c中的元素初始化到數組中
public ArrayDeque(Collection<? extends E> c) {
    allocateElements(c.size());
    addAll(c);
}
// 初始化數組
private void allocateElements(int numElements) {
    elements = new Object[calculateSize(numElements)];
}
// 計算容量,這段代碼的邏輯是算出大於numElements的最接近的2的n次方且不小於8
// 比如,3算出來是8,9算出來是16,33算出來是64
private static int calculateSize(int numElements) {
    int initialCapacity = MIN_INITIAL_CAPACITY;
    // Find the best power of two to hold elements.
    // Tests "<=" because arrays aren't kept full.
    if (numElements >= initialCapacity) {
        initialCapacity = numElements;
        initialCapacity |= (initialCapacity >>>  1);
        initialCapacity |= (initialCapacity >>>  2);
        initialCapacity |= (initialCapacity >>>  4);
        initialCapacity |= (initialCapacity >>>  8);
        initialCapacity |= (initialCapacity >>> 16);
        initialCapacity++;

        if (initialCapacity < 0)   // Too many elements, must back off
            initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
    }
    return initialCapacity;
}

通過構造方法,我們知道預設初始容量是16,最小容量是8。

入隊

入隊有很多方法,我們這裡主要分析兩個,addFirst(e)和addLast(e)。

// 從隊列頭入隊
public void addFirst(E e) {
    // 不允許null元素
    if (e == null)
        throw new NullPointerException();
    // 將head指針減1並與數組長度減1取模
    // 這是為了防止數組到頭了邊界溢出
    // 如果到頭了就從尾再向前
    // 相當於迴圈利用數組
    elements[head = (head - 1) & (elements.length - 1)] = e;
    // 如果頭尾挨在一起了,就擴容
    // 擴容規則也很簡單,直接兩倍
    if (head == tail)
        doubleCapacity();
}
// 從隊列尾入隊
public void addLast(E e) {
    // 不允許null元素
    if (e == null)
        throw new NullPointerException();
    // 在尾指針的位置放入元素
    // 可以看到tail指針指向的是隊列最後一個元素的下一個位置
    elements[tail] = e;
    // tail指針加1,如果到數組尾了就從頭開始
    if ( (tail = (tail + 1) & (elements.length - 1)) == head)
        doubleCapacity();
}

(1)入隊有兩種方式,從隊列頭或者從隊列尾;

(2)如果容量不夠了,直接擴大為兩倍;

(3)通過取模的方式讓頭尾指針在數組範圍內迴圈;

(4)x & (len - 1) = x % len,使用&的方式更快;

擴容

private void doubleCapacity() {
    assert head == tail;
    // 頭指針的位置
    int p = head;
    // 舊數組長度
    int n = elements.length;
    // 頭指針離數組尾的距離
    int r = n - p; // number of elements to the right of p
    // 新長度為舊長度的兩倍
    int newCapacity = n << 1;
    // 判斷是否溢出
    if (newCapacity < 0)
        throw new IllegalStateException("Sorry, deque too big");
    // 新建新數組
    Object[] a = new Object[newCapacity];
    // 將舊數組head之後的元素拷貝到新數組中
    System.arraycopy(elements, p, a, 0, r);
    // 將舊數組下標0到head之間的元素拷貝到新數組中
    System.arraycopy(elements, 0, a, r, p);
    // 賦值為新數組
    elements = a;
    // head指向0,tail指向舊數組長度表示的位置
    head = 0;
    tail = n;
}

擴容這裡遷移元素可能有點繞,請看下麵這張圖來理解。

qrcode

出隊

出隊同樣有很多方法,我們主要看兩個,pollFirst()和pollLast()。

// 從隊列頭出隊
public E pollFirst() {
    int h = head;
    @SuppressWarnings("unchecked")
    // 取隊列頭元素
    E result = (E) elements[h];
    // 如果隊列為空,就返回null
    if (result == null)
        return null;
    // 將隊列頭置為空
    elements[h] = null;     // Must null out slot
    // 隊列頭指針右移一位
    head = (h + 1) & (elements.length - 1);
    // 返回取得的元素
    return result;
}
// 從隊列尾出隊
public E pollLast() {
    // 尾指針左移一位
    int t = (tail - 1) & (elements.length - 1);
    @SuppressWarnings("unchecked")
    // 取當前尾指針處元素
    E result = (E) elements[t];
    // 如果隊列為空返回null
    if (result == null)
        return null;
    // 將當前尾指針處置為空
    elements[t] = null;
    // tail指向新的尾指針處
    tail = t;
    // 返回取得的元素
    return result;
}

(1)出隊有兩種方式,從隊列頭或者從隊列尾;

(2)通過取模的方式讓頭尾指針在數組範圍內迴圈;

(3)出隊之後沒有縮容哈哈^^

前面我們介紹Deque的時候說過,Deque可以直接作為棧來使用,那麼ArrayDeque是怎麼實現的呢?

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

public E pop() {
    return removeFirst();
}

是不是很簡單,入棧出棧只要都操作隊列頭就可以了。

總結

(1)ArrayDeque是採用數組方式實現的雙端隊列;

(2)ArrayDeque的出隊入隊是通過頭尾指針迴圈利用數組實現的;

(3)ArrayDeque容量不足時是會擴容的,每次擴容容量增加一倍;

(4)ArrayDeque可以直接作為棧使用;

彩蛋

雙端隊列與雙重隊列?

雙端隊列(Deque)是指隊列的兩端都可以進出元素的隊列,裡面存儲的是實實在在的元素。

雙重隊列(Dual Queue)是指一種隊列有兩種用途,裡面的節點分為數據節點和非數據節點,它是LinkedTransferQueue使用的數據結構。

還記得LinkedTransferQueue嗎?點擊鏈接直達【死磕 java集合之LinkedTransferQueue源碼分析】。


歡迎關註我的公眾號“彤哥讀源碼”,查看更多源碼系列文章, 與彤哥一起暢游源碼的海洋。

qrcode


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.USB匯流排類型: OHCI(Open Host Controller Interface)是支持USB1.1的標準,但它不僅僅是針對USB,UHCI(Universal Host Controller Interface),是Intel主導的對USB1.0、1.1的介面標準,與OHCI不相容EH ...
  • https://codereview.qt-project.org/#/c/236948/2/src/corelib/tools/qalgorithms.h ...
  • 前言 最近在做智慧工廠相關的工作,多多少少瞭解了一點物聯網相關的技術。於是心血來潮,尋思自己可以做點什麼,恰巧之前聽說過一些樹莓派的傳聞,於是就有了這麼一款鬧鐘。 需要說明的是,在看這篇文章之前,你至少應該是一個會裝操作系統的程式猿,懂點 Linux,會些 Python,最主要的是你得有一個女朋友。 ...
  • 所屬網站分類: python基礎 > 模塊,庫 作者:追夢騷年 鏈接:http://www.pythonheidong.com/blog/article/68/ 來源:python黑洞網,專註python資源,python教程,python技術! 獲取當前時間的模塊/方法是什麼? 獲取日期加時間使用 ...
  • 一、Python 簡介 Python定義:是一個免費、開源、跨平臺、動態、面向對象的編程語言。 Python程式的執行(運行)方式有兩種:互動式、文件式 互動式在命令行輸入指令,回城即可得到結果。1.打開終端2.進行互動式:python33.編寫代碼:print(“hello world”)4.離開 ...
  • [TOC] 有名函數(掌握) 我們之前定的函數都是有名函數,它是基於函數名使用。 from func from func from func 匿名函數(掌握) 匿名函數,他沒有綁定名字,使用一次即被收回,加括弧既可以運行。 (x, y) 3 與內置函數聯用(掌握) 匿名函數通常與max()、min( ...
  • Python基礎之容器類型的公共方法,包括 Python內置函數,切片,運算符,完整的for迴圈。其中,Python內置函數包括 內置函數羅列,內置函數使用;切片僅 包含切片的定義和使用;運算符包括 運算符羅列,運算符的使用;完整的for迴圈 包括 完整的for迴圈語法,for迴圈演示,break打... ...
  • 1.包裝類 把八大基本數據類型封裝到一個類中(包裝類),並提供屬性和方法。讓我們更加方便的操作基本數據類型。但包裝類的出現並不是為了取代基本數據類型,也取代不了。 包裝類位於java.lang包中。 Number 類 Number數值類型是byte、double、float、int、long 和 s ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...