ArrayDeque循环队列的部分源码分析

ArrayDeque是一个以循环数组实现的双向队列。

官方是这么描述的:Deque 接口的大小可变数组的实现。数组双端队列没有容量限制;它们可根据需要增加以支持使用。它们不是线程安全的;在没有外部同步时,它们不支持多个线程的并发访问。禁止 null 元素。此类很可能在用作堆栈时快于 Stack,在用作队列时快于 LinkedList。

- ArrayDeque头

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

上面是ArrayDeque定义信息,我们可以看出,它实现了AbstractCollection。

- 成员变量

 transient Object[] elements; //定义一个数组,下面我们可以了解到,他的初始化操作在构造函数中,初始大小是16.

 transient int head;//头指针,指向队列的头

 transient int tail;//尾指针,指向队列的队尾

 private static final int MIN_INITIAL_CAPACITY = 8;//数组最小容量。

下面我们看一下,构造函数。

- 构造函数1

   public ArrayDeque() {
        elements = new Object[16];//无参数构函,循环数组的默认大小是16.
    }

- 构造函数2

 public ArrayDeque(int numElements) {//这里用户提供了容量
        allocateElements(numElements);//构函调用了这个函数,下面我们                       来分析allocateElements();
    }
 private void allocateElements(int numElements) {
     //先定义一个变量作为数组的最小容量,也就是上面定义变量:最小容量为8,如果用户输入的小于8,直接就用最小容量,来创建数组。
        int initialCapacity = MIN_INITIAL_CAPACITY;
        //如果大于8,则将initialCapacity 变成与其最接近并大于等于其自身的2的n次方幂
        if (numElements >= initialCapacity) {
            initialCapacity = numElements;
            initialCapacity |= (initialCapacity >>>  1);
            initialCapacity |= (initialCapacity >>>  2);
            initialCapacity |= (initialCapacity >>>  4);
            initialCapacity |= (initialCapacity >>>  8);
            initialCapacity |= (initialCapacity >>> 16);
            initialCapacity++;
            //如果数组容量太大溢出,直接赋值为2的30次幂
            //官方的注释:Too many elements, must back off,Good                      luck allocating 2 ^ 30 elements
        if (initialCapacity < 0)   
                initialCapacity >>>= 1;
        }
        elements = new Object[initialCapacity];
    }

我们看一下:
initialCapacity = numElements;
initialCapacity |= (initialCapacity >>> 1);
initialCapacity |= (initialCapacity >>> 2);
initialCapacity |= (initialCapacity >>> 4);
initialCapacity |= (initialCapacity >>> 8);
initialCapacity |= (initialCapacity >>> 16);
initialCapacity++;

假设我们输入的是17,initialCapacity 被赋值为17,17的二进制为10001

  • 10001>>>1=1000,10001|1000=11001;
  • 11001>>>2 =110,11001|110=11111;
  • 11111>>>4=1,11111|1=11111;
  • 11111>>>8=0,11111|0=11111;
  • 11111>>>16=0,11111|0=11111;

  • 最后initialCapacity++,也就是11111+1=100000转化为10进制也就是32。32也确实是大于17最近的2的n次幂。
    这个算法的应用是很高效的,时间复杂度是O(1),要比用其他算法求效率要高。

- 构造函数3

public ArrayDeque(Collection<? extends E> c) {
        allocateElements(c.size());//我们上面已经介绍过了
        addAll(c);//把集合元素放在数组中
    }

-部分成员方法

接下来,我们来看主要的添加操作:这个类中的操作主要有两个,一个从头加,一个是从尾加。

    public void addFirst(E e) {
        if (e == null)
            throw new NullPointerException();//健壮性判断
        elements[head = (head - 1) & (elements.length - 1)] = e;//每次向头添加都是,头指针自减并与数组长度-1进行与运算。
        if (head == tail)
            doubleCapacity();//如果循环数组加满,我们就扩容。
    }

我们来分析 elements[head = (head - 1) & (elements.length - 1)] = e;
由于容量总是2的n次幂,所以总有以下规律
设a是一个整型变量,elements是2的n次幂的整型,那么有
当a=-1时
a&elements-1=elements-1 成立
当0<=a<=elements-1
a&elements-1=a
所以一开始head=0时,就向头中插入元素,实际插入的位置是数组的末尾。

我们在来看下面的doubleCapacity() 扩容方法。

   private void doubleCapacity() {
        assert head == tail;
        int p = head;
        int n = elements.length;
        int r = n - 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);//拷贝数组右边,从head开始,拷贝r个长度,放在新数组从0开始的位置上。
        System.arraycopy(elements, 0, a, r, p);
        //拷贝数组左边,从0开始,拷贝p个长度,放在新数组从r开始的位置上。
        elements = a;
        head = 0;
        tail = n;//head和tail指针重新初始化
    }

我们来看从队尾加的方法

 public void addLast(E e) {
        if (e == null)
            throw new NullPointerException();
        elements[tail] = e;//需要注意的是,在队头插入结点是先走了一位在插入,这里队尾是现在当前位置插入,在“向前”走一位。
        if ( (tail = (tail + 1) & (elements.length - 1)) == head)//如果头尾相遇就扩容,这个算法上面已经介绍过了, 和(elements.length - 1)进行与运算是为了尾指针能够自动循环。
            doubleCapacity();
    }

接下来是删除队头的节点

  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;     //将要poll的节点位置设置为空
        head = (h + 1) & (elements.length - 1);//头指针向后挪一位
        return result;
    }

删除队尾结点

 public E pollLast() {
        int t = (tail - 1) & (elements.length - 1);//因为队尾插入的特点,尾指针现在指向的是尾结点的后一位,所以要tail-1来指向当前的尾结点。
        @SuppressWarnings("unchecked")
        E result = (E) elements[t];
        if (result == null)
            return null;
        elements[t] = null;//将取出来的尾结点的位置,置空。
        tail = t;
        return result;
    }
//只取头结点而不删除
@SuppressWarnings("unchecked")
    public E peekFirst() {
        // elements[head] is null if deque empty
        return (E) elements[head];
    }
//只取尾结点的值而不删除
    @SuppressWarnings("unchecked")
    public E peekLast() {
        return (E) elements[(tail - 1) & (elements.length - 1)];
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值