Queue、Deque、ArrayDeque源码分析

ArrayDeque,Collection框架中不起眼的一个类有删改

继承关系

8923455-6d815404deb0ef23.png
继承关系

类名中的 Array姓氏,它是基于数组实现的。

类注释中,有句话引起了我的注意:

/**
 * This class is likely to be faster than
 * {@link Stack} when used as a stack, and faster than {@link LinkedList}
 * when used as a queue.
 */

(Stack先不管)注释中后半句说,ArrayDeque作为队列时比LinkedList快,看看它是怎么办到的!

属性:

transient Object[] elements;    //基于数组实现
transient int head;    //头指针
transient int tail;    //尾巴指针

构造器

private static final int MIN_INITIAL_CAPACITY = 8; // 默认大小

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;
}
// The main insertion and extraction methods are addFirst,
// addLast, pollFirst, pollLast. The other methods are defined in
// terms of these.

方法

以上注释有云,核心方法就4个,我们从add方法入手。

插入

  • addFirst
public void addFirst(E e) {
    if (e == null)
        throw new NullPointerException();
    elements[head = (head - 1) & (elements.length - 1)] = e;    //关键
    if (head == tail)
        doubleCapacity();
}

head = (head - 1) & (elements.length - 1),玄机就在这里。如果你对1.8的HashMap足够了解,就会知道hashmap的数组大小同样始终是2的次幂。其中很重要的一个原因就是:当lengh是2的次幂的时候,某数字 x 的操作 x & (length - 1) 等价于 x % length,而对二进制的计算机来说 & 操作要比 % 操作效率更好
而且head = (head - 1) & (elements.length - 1),(head初始值0)第一次就将head指针定位到数组末尾了。

画图分析下:


8923455-ff7361ac7c6ca16b.png
队列首添加元素

可见,head指针从后向前移动。

addLast

public void addLast(E e) {
    if (e == null)
        throw new NullPointerException();
    elements[tail] = e;
    if ( (tail = (tail + 1) & (elements.length - 1)) == head)
        doubleCapacity();
}
8923455-fa73cbf8438c0420.png
队列尾部添加元素

addLast和addFirst原理相同,只是addLast控制tail指针,从前向后移动!

上图中再做一次add操作,指针将会重合。比如,再一次addFirst之后:


8923455-96a786e3bed593a4.png
扩容操作
if (head == tail)
        doubleCapacity();    //扩容触发

扩容

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;    //左移,等价乘2,依然保持2的次幂
    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;
}
8923455-af13b2b1011657db.png
扩容实现

通过数组拷贝和重新调整指针,完成了扩容。

至于pollFirst、pollLast是addFirst、addLast的相反操作,原理相似,不多做分析。

作为队列时,ArrayDeque效率为什么会比LinkedList更好?

我认为:因为只操作首尾元素,不会像ArrayList那样插入或删除中间元素,不会设计到大量的复制移动操作,并且基于数组。所以效率更高。

猜你喜欢

转载自blog.csdn.net/weixin_33862514/article/details/87014903
今日推荐