ArrayDeque源码图析

来源:互联网 发布:淘宝有货到付款的产品 编辑:程序博客网 时间:2024/06/02 13:03

    事情是这样的

    在一次开发当中,我想用到队列的相关特性,这次队列不用考虑在并发情况下的安全特性,并发包里面的数据结构就不用考虑了。于是我找啊找,到了jdk中队列的实现有LinkedList,ArrayDeque,PriorityQueue。因为这次使用不需要考虑优先级,首先就排除了PriorityQueue。剩下LinkedList,ArrayDeque了。很纠结啊,到底用谁呢,LinkdList很熟悉,是一个双向链表,对于头部和尾部的增删操作支持都非常好,再看看ArrayDeque,他认识我,我不是很认识它,我就带着好奇的心态去详细的了解这个结构。

    看到ArrayDeque 描述说,如果你想把LinkedList当作队列来用,那么ArrayDeque会更快。哈哈,正合我意,为什么这么快呢?让我们来详细了解这个结构吧。首先ArrayDeque 是一个用数组实现的没有容量限制的双端队列。所谓双端队列,是可以在队列的头部和尾部都进行进行如添加和删除操作的队列。ArrayDeque继承了AbstractCollection,实现了Deque。ArrayDeque的增删操作都是围绕head下标和tail下标来对数组进行操作的。对一块连续的内存进行读取,设置某内存的值都是很快的。

优缺点:

1.没有容量限制。

2.多线程环境下不支持并发访问。

3.不支持插入空元素。

4.当把LinkedList 用做queue 的时候,把Stack 用做stack 时,ArrayDeque 速度会比他们更快。

到底有多快,数据说话

当我们分别用ArrayDeque和LinkedList 分别对1百万个数进行入队,出队的操作时,他们所花费的时间:

package java_util_t;import java.util.ArrayDeque;import java.util.LinkedList;import java.util.Queue;public class ArrayDequeDemo {        /**     * @param args     */    public static void main(String[] args) {        int time = 1000000;                long s1 = System.currentTimeMillis();        Queue<Integer> queue1 = new ArrayDeque<Integer>(time);        for (int i = 0; i < time; i++) {            queue1.offer(i);        }        for (int i = 0; i < time; i++) {            queue1.poll();        }        System.out.println("ArrayDeque cost time:" + (System.currentTimeMillis() - s1));                long s2 = System.currentTimeMillis();        Queue<Integer> queue2 = new LinkedList<Integer>();        for (int i = 0; i < time; i++) {            queue2.offer(i);        }        for (int i = 0; i < time; i++) {            queue2.poll();        }        System.out.println("LinkedList cost time:" + (System.currentTimeMillis() - s2));    }}


  源码分析

对于ArrayDeque源码的分析主要从Queue接口的实现来分析,就是说一端入队一端出队的结构来分析。

1.ArrayDeque 的构造

 当我们我们初始化一个Queue<Integer> deque = new ArrayDeque<Integer>(6); 时候。会默认选一个>6的最小的2^n。


    public ArrayDeque() {//底层默认大小为16 的Object 数组        elements = (E[]) new Object[16];    }        public ArrayDeque(int numElements) {//构造一个 容量>= numElients 的队列         allocateElements(numElements);//分配底层数组元素,默认为null    }    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.        if (numElements >= initialCapacity) {//这里设计很巧妙,会寻找一个>numElements 的2的n次幂的一个初始容量,如果数值越界了导致出现了负数,就给个2^30            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 = (E[]) new Object[initialCapacity];    }

2.ArrayDeque 入队操作

    Queue<Integer> deque = new ArrayDeque<Integer>(6);
    deque.offer(1);

    当我们执行这个操作的时候:

    

public boolean offer(E e) {//队列入队操作    return offerLast(e);//队列尾部入队操作}public boolean offerLast(E e) {    addLast(e);    return true;}public void addLast(E e) {    if (e == null)//不允许插入Null元素,否则抛出异常        throw new NullPointerException();    elements[tail] = e;//设置tail下标元素    if ( (tail = (tail + 1) & (elements.length - 1)) == head)//你可以把数组队列想象成一个环形数组,当tail+1超过数组最后一个下标时,&操作返回0,此时tail ==head doubleCapacity();//扩容}private void doubleCapacity() { //入队时,如果队列元素为空,就会扩容    assert head == tail;     int p = head;     int n = elements.length;     int r = n - p;     int newCapacity = n << 1;//构造或者上一次扩容时队列的容量总是为2^n,当扩充容量为当前容量的2倍时,只需一个左移操作即可。    if (newCapacity < 0) throw new IllegalStateException("Sorry, deque too big");     Object[] a = new Object[newCapacity];    System.arraycopy(elements, p, a, 0, r);//把旧队列head下标到(element.length-1下标)的元素复制到新队列的开头。(1)部分    System.arraycopy(elements, 0, a, r, p);//复制0~head下标之间的元素到到新队列的(1)部分之后。    elements = (E[])a; //替换旧的元素数组    head = 0;//重新设置头结点     tail = n;//重新设置尾节点}

扩容图示:

当我们执行以下代码时,会发生扩容

Queue<Integer> deque = new ArrayDeque<Integer>(6);for (int i = 1; i <= 7; i++) {deque.offer(i);}//第(1)部分deque.offer(8);//第(2)部分,当执行这串代码的时候会扩容

当程序跑到第(1)部分时,已经入队了7个元素此时,


继续入队8会发生扩容:

deque.offer(8);此时这个操作会判断tail == head 将当前容量设置为双倍容量

3.ArrayDeque 出队操作

我们执行如下操作

Queue<Integer> deque = new ArrayDeque<Integer>(6);
        deque.offer(1);
        deque.poll();

当出队的时候:


public E poll() {//队列出队    return pollFirst();//出队第一个元素}public E pollFirst() {    int h = head;    E result = elements[h]; //出队操作时,会返回第一个元素给调用者    if (result == null)        return null;    elements[h] = null;     //出队操作的实质是通过将队列的头元素设置为null,然后再将head下标往后移动一位。    head = (h + 1) & (elements.length - 1);//这里就是上一步说的将head下标往后移动一位,    //而这个head = (h + 1) & (elements.length - 1) 操作保证head在往后移动的时候不会数组越界    return result;}

4.ArrayDeque的一些访问操作

public E element() {//查看队列头元素,其实跟peek()唯一不同的是,当队列为空时,element()会抛出NoSuchElementException    return getFirst();//获取头元素}public E getFirst() {    E x = elements[head];//直接返回数组head下标指向的元素    if (x == null)        throw new NoSuchElementException();    return x;} public E peek() {//查看队列头元素,当队列为空时直接返回null,不抛异常       return peekFirst(); }  public E peekFirst() {        return elements[head]; // 如果队列为空时,返回elments[head] = null;  }

5.ArrayDeque 其他的一些操作

public void clear() {//清空操作    int h = head;    int t = tail;    if (h != t) { // head != tail 表示队列元素 不为空        head = tail = 0;//设置head 和 tail 初始状态        int i = h;        int mask = elements.length - 1;        do {            elements[i] = null;//配合循环将所有元素设置为null            i = (i + 1) & mask;        } while (i != t);    }}public boolean contains(Object o) {//判断队列是否包含该元素    if (o == null)        return false;    int mask = elements.length - 1;    int i = head;    E x;    while ( (x = elements[i]) != null) {//从head元素向后猪哥判断,是否equals        if (o.equals(x))            return true;        i = (i + 1) & mask;    }    return false;}  public int size() {//获取队列元素个数,(tail - head) & (elements.length - 1)保证大小在有效范围内。        return (tail - head) & (elements.length - 1);    }  public boolean isEmpty() {//入队操作,tail+=1;出队操作head+=1;当一直出队元素的时候,head一直+,会==tail,此时head==tail都指向null元素。        return head == tail;    }








0 0
原创粉丝点击