ArrayDeque

ArrayDeque

原文見Java 容器源碼分析之 Deque 與 ArrayDeque氢卡。

概述

ArrayDequeDeque接口的一個實現(xiàn),使用了可變數(shù)組镇饺,所以沒有容量上的限制愿卸。同時抄瓦,ArrayDeque是線程不安全的,在沒有外部同步的情況下蚕泽,不能再多線程環(huán)境下使用蛇更。ArrayDequeDeque的實現(xiàn)類,可以作為棧來使用赛糟,效率高于Stack派任;也可以作為隊列來使用,效率高于LinkedList璧南。需要注意的是掌逛,ArrayDeque不支持null值。

結構

//用數(shù)組存儲元素
transient Object[] elements; // non-private to simplify nested class access
//頭部元素的索引
transient int head;
//尾部下一個將要被加入的元素的索引
transient int tail;
//最小容量司倚,必須為2的冪次方
private static final int MIN_INITIAL_CAPACITY = 8;

ArrayDeque底層使用了數(shù)組來存儲數(shù)據(jù)豆混,同時用兩個intheadtail來表示頭部和尾部。不過需要注意的是tail并不是尾部元素的索引动知,而是尾部元素的下一位皿伺,即下一個將要被加入的元素的索引。

初始化

ArrayDeque有三個構造函數(shù)來初始化盒粮,除了無參的構造函數(shù)使用了默認容量鸵鸥,其它兩個構造函數(shù)會通過allocateElements來計算初始容量。

public ArrayDeque() {
    elements = new Object[16];
}

public ArrayDeque(int numElements) {
    allocateElements(numElements);
}

public ArrayDeque(Collection<? extends E> c) {
    allocateElements(c.size());
    addAll(c);
}

ArrayDeque 對數(shù)組的大小(即隊列的容量)有特殊的要求丹皱,必須是 2^n妒穴。我們來看一下allocateElements方法。

private void allocateElements(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
    }
    elements = new Object[initialCapacity];
}

這里的實現(xiàn)很有意思摊崭,對于一個小于2^30的值讼油,經(jīng)過五次右移和位或操作后,可以得到一個2^k - 1的值呢簸。最后再將這個值+1矮台,得到2^k。通過這個方法根时,可以將一個任意的初始值轉化為2^n的值瘦赫,不過有一點不足在于,如果本身傳進來的值就是2^n的值啸箫,那么經(jīng)過轉化會變成2^(n+1)耸彪,所以我們在不用刻意去傳入2^n的值。還有一點在于忘苛,如果傳入的值大于等于2^30蝉娜,那么經(jīng)過轉化會變成負值唱较,即< 0,此時會把初始值設置為2^30召川,即最大的容量只有2^30南缓。

添加元素

向末尾添加元素

public void addLast(E e) {
    if (e == null)
        throw new NullPointerException();
    //tail 中保存的是即將加入末尾的元素的索引
    elements[tail] = e;
    //tail 向后移動一位
    //把數(shù)組當作環(huán)形的,越界后到0索引
    if ( (tail = (tail + 1) & (elements.length - 1)) == head)
        //tail 和 head相遇荧呐,空間用盡汉形,需要擴容
        doubleCapacity();
}

ArrayDeque中,數(shù)組是作為環(huán)形來使用的倍阐,正常情況下在末尾添加元素后概疆,tail=tail+1是要判斷是否越界,如果越界峰搪,會變?yōu)閺乃饕?code>0開始岔冀。參考如下圖片,當H添加到索引7后概耻,tail值會+1,此時tail=8使套,但是越界了,所以應該將tail設置為0鞠柄。

環(huán)形數(shù)組
環(huán)形數(shù)組

但是為什么這里并沒有判斷越界呢侦高?關鍵在于(tail = (tail + 1) & (elements.length - 1)) == head這段代碼。這段代碼可以拆分為tail = (tail + 1) & (elements.length - 1)tail == head厌杜,這樣會比較清晰奉呛,前一段代碼是為了獲取正確的tail索引值,后一段代碼是為了判斷數(shù)組是否滿了期奔。之前要求數(shù)組的大小必須為2^n值侧馅,就是為了能夠不通過條件判斷危尿,直接使用位操作在環(huán)形數(shù)組中獲取下一個正確的索引值呐萌。

我們驗證一下tail = (tail + 1) & (elements.length - 1)的正確性:

length = 2^n,二進制表示為: 第 n 位為1谊娇,低位 (n-1位) 全為0
length - 1 = 2^n-1肺孤,二進制表示為:低位(n-1位)全為1

如果 tail + 1 <= length - 1,則位與后低 (n-1) 位保持不變济欢,高位全為0
如果 tail + 1 = length赠堵,則位與后低 n 全為0,高位也全為0法褥,結果為 0

所以當tail+1 <= length - 1茫叭,此時數(shù)組并沒有越界,(tail + 1) & (elements.length - 1)后得到的還是tail+1半等。如果tail + 1 = length揍愁,此時數(shù)組越界了呐萨,(tail + 1) & (elements.length - 1)后得到0

所以通過(tail + 1) & (elements.length - 1)可以跳過條件判斷在環(huán)形數(shù)組中獲取正確的索引值莽囤,然后再判斷新的tail是否等于head谬擦,如果結果為true,那么數(shù)組已經(jīng)滿了朽缎,需要擴容惨远,即doubleCapacity()

向頭部添加元素

public void addFirst(E e) {
    if (e == null) //不支持值為null的元素
        throw new NullPointerException();
    elements[head = (head - 1) & (elements.length - 1)] = e;
    if (head == tail)
        doubleCapacity();
}

其實向頭部添加元素和向尾部添加元素原理是相同的话肖,都是通過位操作高效獲取新的索引值北秽。

擴容

無論是從頭部還是從尾部添加元素,都會判斷tail==head最筒,如果兩個索引相遇羡儿,說明數(shù)組空間已滿,需要擴容操作是钥。見下圖:

數(shù)組擴容
數(shù)組擴容

private void doubleCapacity() {
    assert head == tail; //擴容時頭部索引和尾部索引肯定相等
    int p = head;
    int n = elements.length;
    //頭部索引到數(shù)組末端(length-1處)共有多少元素
    int r = n - p; // number of elements to the right of p
    //容量翻倍掠归,相當于 2 * n
    int newCapacity = n << 1;
    //容量過大,溢出了
    if (newCapacity < 0)
        throw new IllegalStateException("Sorry, deque too big");
    //分配新空間
    Object[] a = new Object[newCapacity];
    //復制頭部索引到數(shù)組末端的元素到新數(shù)組的頭部
    System.arraycopy(elements, p, a, 0, r);
    //復制其余元素
    System.arraycopy(elements, 0, a, r, p);
    elements = a;
    //重置頭尾索引
    head = 0;
    tail = n;
}

移除元素

ArrayDeque支持從頭尾兩端移除元素悄泥,remove方法是通過poll來實現(xiàn)的虏冻。因為是基于數(shù)組的,在了解了環(huán)的原理后這段代碼就比較容易理解了弹囚。

public E pollFirst() {
    int h = head;
    @SuppressWarnings("unchecked")
    E result = (E) elements[h];
    // Element is null if deque empty
    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];
    if (result == null)
        return null;
    elements[t] = null;
    tail = t;
    return result;
}
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末厨相,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鸥鹉,更是在濱河造成了極大的恐慌蛮穿,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,865評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件毁渗,死亡現(xiàn)場離奇詭異践磅,居然都是意外死亡,警方通過查閱死者的電腦和手機灸异,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,296評論 3 399
  • 文/潘曉璐 我一進店門府适,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人肺樟,你說我怎么就攤上這事檐春。” “怎么了么伯?”我有些...
    開封第一講書人閱讀 169,631評論 0 364
  • 文/不壞的土叔 我叫張陵疟暖,是天一觀的道長。 經(jīng)常有香客問我,道長俐巴,這世上最難降的妖魔是什么朋贬? 我笑而不...
    開封第一講書人閱讀 60,199評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮窜骄,結果婚禮上锦募,老公的妹妹穿的比我還像新娘。我一直安慰自己邻遏,他們只是感情好糠亩,可當我...
    茶點故事閱讀 69,196評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著准验,像睡著了一般赎线。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上糊饱,一...
    開封第一講書人閱讀 52,793評論 1 314
  • 那天垂寥,我揣著相機與錄音,去河邊找鬼另锋。 笑死滞项,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的夭坪。 我是一名探鬼主播文判,決...
    沈念sama閱讀 41,221評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼室梅!你這毒婦竟也來了戏仓?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 40,174評論 0 277
  • 序言:老撾萬榮一對情侶失蹤亡鼠,失蹤者是張志新(化名)和其女友劉穎赏殃,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體间涵,經(jīng)...
    沈念sama閱讀 46,699評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡仁热,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,770評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了浑厚。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片股耽。...
    茶點故事閱讀 40,918評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖钳幅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情炎滞,我是刑警寧澤敢艰,帶...
    沈念sama閱讀 36,573評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站册赛,受9級特大地震影響钠导,放射性物質(zhì)發(fā)生泄漏震嫉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,255評論 3 336
  • 文/蒙蒙 一牡属、第九天 我趴在偏房一處隱蔽的房頂上張望票堵。 院中可真熱鬧,春花似錦逮栅、人聲如沸悴势。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,749評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽特纤。三九已至,卻和暖如春侥加,著一層夾襖步出監(jiān)牢的瞬間捧存,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,862評論 1 274
  • 我被黑心中介騙來泰國打工担败, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留昔穴,地道東北人。 一個月前我還...
    沈念sama閱讀 49,364評論 3 379
  • 正文 我出身青樓提前,卻偏偏與公主長得像傻咖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子岖研,可洞房花燭夜當晚...
    茶點故事閱讀 45,926評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 我們知道隊列這種數(shù)據(jù)結構的物理實現(xiàn)方式主要還是兩種卿操,一種是鏈隊列(自定義節(jié)點類),另一種則是使用數(shù)組實現(xiàn)孙援,兩者各有...
    Single_YAM閱讀 9,932評論 1 5
  • ArrayDeque 的工作原理(Android 7.1源碼) 簡介 ArrayDeque 是以循環(huán)數(shù)組形式實現(xiàn)的...
    Nichool閱讀 1,079評論 0 1
  • Java集合中有一個Stack的類害淤,這個類基于Vector集合實現(xiàn),但是效率較低拓售。ArrayDeque在官方文檔中...
    zhanglbjames閱讀 545評論 0 4
  • 引言 隊列窥摄、棧是最基礎的數(shù)據(jù)結構中的兩種,非常簡單础淤,但也很重要崭放。隊列的規(guī)則是:先進先出(First In Firs...
    xiaoshua閱讀 1,304評論 3 1
  • 我要說大家好,我現(xiàn)在說點什么呢鸽凶。我一天到晚不知道要該干點什么币砂。每天要做點什么,心里才感到踏實玻侥。所以我現(xiàn)在不踏實决摧,每...
    河之閱讀 417評論 0 0