【java容器的刻意练习】【十三】ArrayDeque的源码分析(一)

我们在学习ArrayList的时候,知道其底层是数组。而在学习LinkedList时候,知道其实现了Deque接口。

那么,这篇讲到的ArrayDeque,就是底层是数组,又实现了Deque接口的一种容器。

为什么需要这样的容器呢?这种容器有什么用呢?

先看看ArrayDeque的接口:

ArrayDeque继承关系
ArrayDeque接口

先看构造函数:

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


    public ArrayDeque(int numElements) {
        elements =
            new Object[(numElements < 1) ? 1 :
                       (numElements == Integer.MAX_VALUE) ? Integer.MAX_VALUE :
                       numElements + 1];
    }

原来内部结构果然是elements数组。
无参构造函数原来是分配16+1=17的数组大小。
传参的构造函数分配numElements+1个数组大小。传负数就创建1+1=2个。传入超过MAX_VALUE = 0x7fffffff就只能创建MAX_VALUE = 0x7fffffff个。

再看addFirst(E e)插入头步元素的函数:

    public void addFirst(E e) {
        if (e == null)
            throw new NullPointerException();
        final Object[] es = elements;
        es[head = dec(head, es.length)] = e;
        if (head == tail)
            grow(1);
    }

`es[head = dec(head, es.length)] = e;拆开来看就是:

head = dec(head, es.length);
es[head] = e;

那么,这个head是多少呢?

    /**
     * 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 0 <= head < elements.length equal to tail if
     * the deque is empty.
     */
    transient int head;

原来,head是指向数组头部的索引。初始化是0。取值范围是大于等于0,而小于数组长度。如果等于尾部索引head == tail的话,就证明是空的队列。

dec是啥函数?

    /**
     * Circularly decrements i, mod modulus.
     * Precondition and postcondition: 0 <= i < modulus.
     */
    static final int dec(int i, int modulus) {
        if (--i < 0) i = modulus - 1;
        return i;
    }

注释提到:循环减i,与系数取模。 前置条件和后置条件:0 <= i <系数。

什么意思?这里传入i就是head,值是0。--0就是-1,小于0。所以呢,head = es.length - 1。如果加入的是第一个元素,那么head = 17 - 1 = 16 。

计算出head值后,执行es[head] = e;就是把e存放在数组的第16个位置,就是倒数第一个位置。

如果继续调用addFirst加入头部,这时候head = 15,放在倒数第二个位置。

原来,ArrayDeque的addFirst是对数组索引逆序使用,以实现元素插入头部的功能。想象下,就好像一群胆小的人去坐过山车。胆小的人抢着先坐最后一个座位。第二个胆小的只能坐倒数第二个座位。我们可以这样建立这样的联想:

ArrayDeque的addFirst相当于胆小鬼坐过山车抢后排座位,从后往前坐。

ArrayDeque添加元素示意图

继续看下一句:

        if (head == tail)
            grow(1);

当插入一个元素后,如果头部与尾巴重叠,证明数组满了。这时候调用grow(1)扩容。

    /**
     * Increases the capacity of this deque by at least the given amount.
        对这个双向队列进行扩容,至少也要达到给定的容量
     *
     * @param needed the required minimum extra capacity; must be positive
     */
    private void grow(int needed) {
        // overflow-conscious code
        // 保存扩容前的长度
        final int oldCapacity = elements.length;
        // 扩容后的长度
        int newCapacity;
        
        // Double capacity if small; else grow by 50%
        // jump 为待扩容长度。如果原长度小于64,就每次扩容原来的长度+2。如果大于64,每次扩容50%
        int jump = (oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1);

        // 1、如果待扩容长度小于用户期望扩容的长度,就调用 newCapacity(needed, jump)
        // 2、或者扩容超最大限制了,也调用 newCapacity(needed, jump)
        // 3、最终得到期待的长度newCapacity 
        if (jump < needed
            || (newCapacity = (oldCapacity + jump)) - MAX_ARRAY_SIZE > 0)
            newCapacity = newCapacity(needed, jump);

        // 创建个新的扩容后长度的数组,把老数组元素拷贝过去
        final Object[] es = elements = Arrays.copyOf(elements, newCapacity);

        // Exceptionally, here tail == head needs to be disambiguated
        // 处理下如果 tail == head 的异常情况
        if (tail < head || (tail == head && es[head] != null)) {
            // wrap around; slide first leg forward to end of array
            // 把原来的元素重新放回扩容后数组末尾
            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;
        }
    }

newCapacity(needed, jump)也很简单,如下:

    /** Capacity calculation for edge conditions, especially overflow. */
    private int newCapacity(int needed, int jump) {
        final int oldCapacity = elements.length, minCapacity;

        // 如果按期待去扩容,容量大于MAX_ARRAY_SIZE,而且没溢出的话,就只返回Integer.MAX_VALUE
        if ((minCapacity = oldCapacity + needed) - MAX_ARRAY_SIZE > 0) {
            if (minCapacity < 0)
                throw new IllegalStateException("Sorry, deque too big");
            return Integer.MAX_VALUE;
        }

        // 如果期待扩容的容量,大于计算出来的扩容,就用期待的容量
        if (needed > jump)
            return minCapacity;

        // 否则就返回计算出来的保证不溢出的扩容量
        return (oldCapacity + jump - MAX_ARRAY_SIZE < 0)
            ? oldCapacity + jump
            : MAX_ARRAY_SIZE;
    }

另外,MAX_ARRAY_SIZE注释上面提到,有一些虚拟机需要存储一些头信息在数组里,如果扩容到Integer.MAX_VALUE会造成内存溢出,所以才有如下定义:

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

再看看addLast,果然跟addFirst差不多。

    public void addLast(E e) {
        if (e == null)
            throw new NullPointerException();
        final Object[] es = elements;
        es[tail] = e;
        if (head == (tail = inc(tail, es.length)))
            grow(1);
    }

而根据我们之前分析,offerFirstofferLast只是增加了返回值true,所以就不额外分析了。

好,今天我们先给出一些结论:

  • ArrayDeque的底层是数组,默认长度是17,会自动扩容
  • ArrayDeque的addFirst相当于胆小鬼坐过山车抢后排座位,从后往前坐。当坐满人的时候,会自动扩容。
  • 自动扩容的规则是:在数组小于64长度时候,扩容原长度2倍+2;大于64时扩容50%。
  • 扩容对溢出做了很多校验,尽量保证总长度不超过MAX_ARRAY_SIZE
  • 扩容后,所有元素仍然处于新数组的末尾。
  • 通过Head、Tail下标来添加元素,时间复杂度O(1)。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容