ArrayDeque源码剖析

1、描述

底层:可变的循环数组

LinkedList实现了队列Queue接口和双端队列Deque,Java容器类中海油一个双端队列的实现类ArrayDeque,它是基于数组实现的

2、实现原理

ArrayDeque内部主要有如下实例变量:

 /**
     * The array in which the elements of the deque are stored.
     * The capacity of the deque is the length of this array, which is
     * always a power of two. The array is never allowed to become
     * full, except transiently within an addX method where it is
     * resized (see doubleCapacity) immediately upon becoming full,
     * thus avoiding head and tail wrapping around to equal each
     * other.  We also guarantee that all array cells not holding
     * deque elements are always null.
     */
    transient Object[] elements; // 存储元素的数组

    /**
     * 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;

ArrayDeque的高效来源于headtail这两个变量

它们使得物理上简单的从头到尾的数组变为了逻辑上循环数组,避免了在头尾操作时的移动

3、构造方法

3.1、 ArrayDeque()

 //分配一个长度为16的数组
    public ArrayDeque() {
        elements = new Object[16];
    }

3.2、ArrayDeque(int numElements)

分配的数组的大小 为分配的实际长度是严格大于numElements并且为2的整数次幂的最小数

  • 如果numElements小于8,就是8
  • 在numElements大于等于8的情况下:分配的实际长度是严格大于numElements并且为2的整数次幂的最小数
 //不是简单的分配给定的长度,而是调用allocateElements
    public ArrayDeque(int numElements) {
        allocateElements(numElements);
    }

  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.
        //如果numElements小于8,就是8
        //在numElements大于等于8的情况下,
        // 分配的实际长度是严格大于numElements并且为2的整数次幂的最小数
        //比如,如果numElements为10,则实际分配32
        /**
         * 为什么要2的幂次数呢? 这样会使得很多操作的效率很高。
         * 为什么要严格大于numElements呢?
         * 因为循环数组必须至少留一个空位,tail变量指向下一个空位,为了容纳numElements个元素,至少需要numElements+1个位置
         */
        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];
    }

3.3、ArrayDeque(Collection<? extends E> c)

 public ArrayDeque(Collection<? extends E> c) {
        //同样调用allocateElments分配数组
        allocateElements(c.size());
        //随后调用addAll,而addAll只是循环调用了add方法
        addAll(c);
    }

4、尾部添加 add方法

  //从尾部添加
    public boolean add(E e) {
        addLast(e);
        return true;
    }

 public void addLast(E e) {
        if (e == null)
            throw new NullPointerException();
        elements[tail] = e;//将元素添加到tail处
        //将tail指向下一个位置
        //如果队列满了,则调用doubleCapacity扩展数组
        //tail的下一个位置是 (tail + 1) & (elements.length - 1)
        //这种位操作是循环数组中的一种常见操作,效率也很高
        if ( (tail = (tail + 1) & (elements.length - 1)) == head)
            doubleCapacity();//将数组扩大为2倍
    }

/**
     * Doubles the capacity of this deque.  Call only when full, i.e.,
     * when head and tail have wrapped around to become equal.
     * 当head与tail相等时才会调用
     * 分配一个长度翻倍的新数组a,
     * 将head右边的元素复制到新数组开头处;再复制左边的元素到新数组中;
     * 最后重新设置head和tail,head设置为0,tail设置为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;//将数组长度扩大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;
    }



循环数组扩容前后对比

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ghykcvkS-1582179313091)(images/01.png)]

5、头部添加 addFirst方法

   //在头部添加,要让head先执行前一个位置,然后再赋值给head所在位置

    public void addFirst(E e) {
        if (e == null)
            throw new NullPointerException();
        //head的前一个位置为 (head - 1) & (elements.length - 1)
        elements[head = (head - 1) & (elements.length - 1)] = e;
        if (head == tail)//若数组已经满了,则扩大循环数组
            doubleCapacity();
    }

6、从头删除removeFirst

 //从头部删除
    public E removeFirst() {
        E x = pollFirst();
        if (x == null)
            throw new NoSuchElementException();
        return x;
    }

 public E pollFirst() {
        int h = head;

        @SuppressWarnings("unchecked")
        E result = (E) elements[h];

        // Element is null if deque empty
        if (result == null)
            return null;
        //将原头部位置为null
        elements[h] = null;     // Must null out slot
        //将head设置为下一个位置,下一个位置为(h+1)&(elements.lengh-1)
        head = (h + 1) & (elements.length - 1);
        return result;
    }


7、查看长度 size()

ArrayDeque没有单独的字段维护长度,其size方法的代码为:

 //ArrayDeque没有单独的字段维护长度
    public int size() {
        return (tail - head) & (elements.length - 1);
    }

8、检查给定元素是否存在contains

 public boolean contains(Object o) {
        //在ArrayDeque中,有效元素不允许为null
        if (o == null)
            return false;

        int mask = elements.length - 1;
        //从head开始遍历并进行对比,循环过程没有使用tail
        //而是到元素为null就结束,这是因为在ArrayDeque中,有效元素不允许为null
        int i = head;
        Object x;
		//遍历整个数组
        while ( (x = elements[i]) != null) {
            if (o.equals(x))
                return true;
            i = (i + 1) & mask;
        }
        return false;
    }

9、toArray方法

 public Object[] toArray() {
        //转换为数组
        return copyElements(new Object[size()]);
    }

//拷贝元素
private <T> T[] copyElements(T[] a) {
        if (head < tail) {
            //如果head小于tail,就是从head开始复制size个
            System.arraycopy(elements, head, a, 0, size());
        } else if (head > tail) {
            //否则复制逻辑与doubleCapaity方法中的类似
            //先复制从head到末尾的部分,然后复制从0到tail的部分
            int headPortionLen = elements.length - head;
            System.arraycopy(elements, head, a, 0, headPortionLen);
            System.arraycopy(elements, 0, a, headPortionLen, tail);
        }
        return a;
    }


10、小结

ArrayDeque内部就是一个动态扩展的循环数组,通过headtail变量维护数组的开始和结尾,

数组的长度为2的幂次方,使用高效的位操作进行各种判断,以及对head和tail进行维护

·ArrayDeque特点分析:`

ArrayDeque实现了双端队列,内部使用循环数组实现,这决定了它由如下特点:

  • 在两端添加、删除元素的效率很高,动态扩展需要内存分配以及数组复制开销可以被平摊,具图来说,添加N个元素的效率为O(N)
  • 根据元素内容查找和删除的效率比较目的,为O(N)
  • ArrayListLinkedList不同,没有索引位置的概念,不能根据索引位置进行操作

ArrayDeque和LinkedList都实现了Deque接口,应该用哪一个呢?

  • 如果只需要Deque接口,从两端进行操作,一般而言,ArrayDeque效率高一些,应该被优先使用;
  • 如果同时需要根据索引位置进行操作,或者经常需要在中间进行插入和删除,则应该选LinkedList
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值