Java8 ArrayDeque 源碼解析

前言

今天我們來(lái)看看 ArrayDeque,可以說(shuō),之前使用的隊(duì)列的場(chǎng)景不多,所以也沒(méi)有深入研究隊(duì)列,但最近在做 LeetCode 的時(shí)候,很多時(shí)候使用隊(duì)列會(huì)有想不到的功效,比如我們?cè)趯?xiě) BFS 廣度優(yōu)先遍歷的時(shí)候,或者在寫(xiě) 二叉樹(shù)的層次遍歷,非遞歸前序,中序,后序遍歷,都會(huì)用到這個(gè)結(jié)構(gòu),如果還不會(huì)的同學(xué),趕緊去復(fù)習(xí)一波吧,非??傄材転槟愕墓P面試加不少分,很多時(shí)候當(dāng)面試官問(wèn)道我分析過(guò)的源碼時(shí),心里那個(gè)叫酸爽啊,令面試官刮目相看。閑話不多說(shuō)了,讓我們深入了解一下我們的主角

前世今生

實(shí)現(xiàn)了 Deque 接口,Deque 繼承了 Queue

public class ArrayDeque<E> extends AbstractCollection<E>
                           implements Deque<E>, Cloneable, Serializable {}

public interface Deque<E> extends Queue<E> {}

Queue

在了解 ArrayDeque 之前,我們先來(lái)看看 Queue 有些什么東西,談到隊(duì)列,我們會(huì)想到先進(jìn)先出等特性,我把接口貼出來(lái)給大家看一下,如果你還不熟悉這幾個(gè)方法的區(qū)別,是該反省一下了!??!一定要熟記,這是基本功

    boolean add(E e);

    boolean offer(E e);

    E remove();

    E poll();

    E element();

    E peek();

Deque

由于是雙端隊(duì)列,多了很多接口,一張圖真相,你可能會(huì)說(shuō),記住這些所有的太難了,太多了,但總結(jié)起來(lái),也并不多,在之前的Queue的接口的基礎(chǔ)上,加了 First,Last 的接口,是不是一下子變少了


Screen Shot 2018-09-12 at 11.00.50 PM.png

ArrayDeque

終于到了我們的主角了,然而你可能會(huì)想,是不是也有LinkedDeque,當(dāng)初我也這樣想過(guò),然而我并沒(méi)有在 jdk 里找到這個(gè)數(shù)據(jù)結(jié)構(gòu),但是 LinkedList 卻實(shí)現(xiàn)了 Deque 也就是說(shuō)它取代了自己LinkedDeque,也就沒(méi)有必要多此一舉了

屬性

既然是 Array,那么里面勢(shì)必用數(shù)組存儲(chǔ),記住,使用 Object[] elements,而不是T[] elements,聰明的你能否想到這樣做的目的呢?哈哈。然后是一個(gè)head,tail 的指針。

   transient Object[] elements; // non-private to simplify nested class access

    /**
     * The index of the element at the head of the deque (which is the
     * element that would be removed by remove() or pop()); or an
     * arbitrary number equal to tail if the deque is empty.
     */
    transient int head;

    /**
     * The index at which the next element would be added to the tail
     * of the deque (via addLast(E), add(E), or push(E)).
     */
    transient int tail;

構(gòu)造函數(shù)

默認(rèn)開(kāi)辟了 16 的數(shù)組,一般都是這樣,預(yù)先開(kāi)辟空間,不夠了再擴(kuò)容。如果自己指定了大小,那么將調(diào)用 allocateElements函數(shù),獲取一個(gè) 2 的冪次方的大小的容量,如果你知道 Hashmap的初始化,那么這個(gè)初始化你一定不陌生!至于怎么做到,你可以自己實(shí)踐一下,這樣會(huì)理解的更加透徹!

    public ArrayDeque() {
        elements = new Object[16];
    }
    public ArrayDeque(int numElements) {
        allocateElements(numElements);
    }
 
    public ArrayDeque(Collection<? extends E> c) {
        allocateElements(c.size());
        addAll(c);
    }

    private void allocateElements(int numElements) {
        elements = new Object[calculateSize(numElements)];
    }

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

普通的方法

add 使用 addLast 在末尾追加元素

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

addLast,直接給數(shù)組的末尾元素賦值,之后便是移動(dòng)tail 指針,這里的擴(kuò)容實(shí)現(xiàn)的很有意思,這也對(duì)應(yīng)了為什么容量要為 2的冪次方,當(dāng)數(shù)組大小剛好為 2 的冪次方時(shí),(tail = (tail + 1) & (elements.length - 1) 為零,也就是說(shuō)如果head也為0,那么就需要擴(kuò)容了

    public void addLast(E e) {
        if (e == null)
            throw new NullPointerException();
        elements[tail] = e;
        if ( (tail = (tail + 1) & (elements.length - 1)) == head)
            doubleCapacity();
    }

doubleCapacity 函數(shù),新數(shù)組的大小為兩倍,使用 System.arraycopy 函數(shù)復(fù)制,效率極高。因?yàn)?head 不一定為零,所以在擴(kuò)容的時(shí)候,需要恢復(fù)head = 0;這里我們應(yīng)該推測(cè)出整個(gè)數(shù)組都是存儲(chǔ)數(shù)據(jù),為了方便刪除數(shù)組而不移動(dòng)元素,便使用了指針記錄狀態(tài),這一實(shí)現(xiàn)必須要好好琢磨,下次面試別人的時(shí)候可以問(wèn)一下別人,哈哈,有點(diǎn)小壞

    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];
        System.arraycopy(elements, p, a, 0, r);
        System.arraycopy(elements, 0, a, r, p);
        elements = a;
        head = 0;
        tail = n;
    }

pollFirst 獲取元素,當(dāng)然是從 head 位置獲取元素,這里需要注意的是,head 如果到了數(shù)組末尾,那么又會(huì)通過(guò) (h + 1) & (elements.length - 1) 變?yōu)?0 了

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

pollLast 獲取隊(duì)尾元素,如果隊(duì)尾元素此時(shí)為 0,那么將回到了 數(shù)組末尾,別問(wèn)我怎么知道,老師叫你好好學(xué)二進(jìn)制!

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

其他的不涉及到刪除,添加到操作就顯得尤為簡(jiǎn)單了,直接獲取,就不必多說(shuō)了

    public E getFirst() {
        @SuppressWarnings("unchecked")
        E result = (E) elements[head];
        if (result == null)
            throw new NoSuchElementException();
        return result;
    }

    /**
     * @throws NoSuchElementException {@inheritDoc}
     */
    public E getLast() {
        @SuppressWarnings("unchecked")
        E result = (E) elements[(tail - 1) & (elements.length - 1)];
        if (result == null)
            throw new NoSuchElementException();
        return result;
    }

pop 根據(jù)先進(jìn)先出,pop 應(yīng)該移除隊(duì)首的元素,注意,這里會(huì)拋出異常,如果隊(duì)首為空的話

     * @throws NoSuchElementException {@inheritDoc}
     */
    public E pop() {
        return removeFirst();
    }

小結(jié)

ArrayDeque 看起來(lái)不大,但也有不少東西,知道大概容易,弄懂每一個(gè)細(xì)節(jié)很難,如果你做到了,成功便是你的~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Queue Queue繼承自 Collection,我們先來(lái)看看類結(jié)構(gòu)吧,代碼量比較少,我直接貼代碼了 從方法名上...
    Anonymous___閱讀 952評(píng)論 0 1
  • 今天我們來(lái)介紹下集合Queue中的幾個(gè)重要的實(shí)現(xiàn)類。關(guān)于集合Queue中的內(nèi)容就比較少了。主要是針對(duì)隊(duì)列這種數(shù)據(jù)結(jié)...
    Ruheng閱讀 8,731評(píng)論 4 27
  • 概述 ??首先解釋下,Queue(隊(duì)列),隊(duì)列是一種先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)(First In First Out,F(xiàn)I...
    騎著烏龜去看海閱讀 983評(píng)論 0 1
  • hashmap實(shí)現(xiàn)的數(shù)據(jù)結(jié)構(gòu),數(shù)組、桶等。 如圖所示 JDK 1.7,是以數(shù)組+鏈表組成的,鏈表為相同hash的鍵...
    不需要任何閱讀 942評(píng)論 0 1
  • 只記錄網(wǎng)絡(luò)來(lái)源的各種雜七雜八。每周一輯。題目即是原文地址。有些文字copy過(guò)來(lái),以防將來(lái)死鏈,留存?zhèn)溆谩?洪園|挑...
    果蠅單倍體閱讀 390評(píng)論 0 0

友情鏈接更多精彩內(nèi)容