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

来源:互联网 发布:手机桌面软件 编辑:程序博客网 时间:2024/06/04 19:34

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)];    }
原创粉丝点击