Java8 ArrayDeque 源码解析

前言

今天我们来看看 ArrayDeque,可以说,之前使用的队列的场景不多,所以也没有深入研究队列,但最近在做 LeetCode 的时候,很多时候使用队列会有想不到的功效,比如我们在写 BFS 广度优先遍历的时候,或者在写 二叉树的层次遍历,非递归前序,中序,后序遍历,都会用到这个结构,如果还不会的同学,赶紧去复习一波吧,非常总要,也能为你的笔面试加不少分,很多时候当面试官问道我分析过的源码时,心里那个叫酸爽啊,令面试官刮目相看。闲话不多说了,让我们深入了解一下我们的主角

前世今生

实现了 Deque 接口,Deque 继承了 Queue

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

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

Queue

在了解 ArrayDeque 之前,我们先来看看 Queue 有些什么东西,谈到队列,我们会想到先进先出等特性,我把接口贴出来给大家看一下,如果你还不熟悉这几个方法的区别,是该反省一下了!!!一定要熟记,这是基本功

    boolean add(E e);

    boolean offer(E e);

    E remove();

    E poll();

    E element();

    E peek();

Deque

由于是双端队列,多了很多接口,一张图真相,你可能会说,记住这些所有的太难了,太多了,但总结起来,也并不多,在之前的Queue的接口的基础上,加了 First,Last 的接口,是不是一下子变少了


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

ArrayDeque

终于到了我们的主角了,然而你可能会想,是不是也有LinkedDeque,当初我也这样想过,然而我并没有在 jdk 里找到这个数据结构,但是 LinkedList 却实现了 Deque 也就是说它取代了自己LinkedDeque,也就没有必要多此一举了

属性

既然是 Array,那么里面势必用数组存储,记住,使用 Object[] elements,而不是T[] elements,聪明的你能否想到这样做的目的呢?哈哈。然后是一个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;

构造函数

默认开辟了 16 的数组,一般都是这样,预先开辟空间,不够了再扩容。如果自己指定了大小,那么将调用 allocateElements函数,获取一个 2 的幂次方的大小的容量,如果你知道 Hashmap的初始化,那么这个初始化你一定不陌生!至于怎么做到,你可以自己实践一下,这样会理解的更加透彻!

    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,直接给数组的末尾元素赋值,之后便是移动tail 指针,这里的扩容实现的很有意思,这也对应了为什么容量要为 2的幂次方,当数组大小刚好为 2 的幂次方时,(tail = (tail + 1) & (elements.length - 1) 为零,也就是说如果head也为0,那么就需要扩容了

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

doubleCapacity 函数,新数组的大小为两倍,使用 System.arraycopy 函数复制,效率极高。因为 head 不一定为零,所以在扩容的时候,需要恢复head = 0;这里我们应该推测出整个数组都是存储数据,为了方便删除数组而不移动元素,便使用了指针记录状态,这一实现必须要好好琢磨,下次面试别人的时候可以问一下别人,哈哈,有点小坏

    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 获取元素,当然是从 head 位置获取元素,这里需要注意的是,head 如果到了数组末尾,那么又会通过 (h + 1) & (elements.length - 1) 变为 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 获取队尾元素,如果队尾元素此时为 0,那么将回到了 数组末尾,别问我怎么知道,老师叫你好好学二进制!

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

其他的不涉及到删除,添加到操作就显得尤为简单了,直接获取,就不必多说了

    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 根据先进先出,pop 应该移除队首的元素,注意,这里会抛出异常,如果队首为空的话

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

小结

ArrayDeque 看起来不大,但也有不少东西,知道大概容易,弄懂每一个细节很难,如果你做到了,成功便是你的~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容