[置顶] ArrayDeque源码图析

    事情是这样的

    在一次开发当中,我想用到队列的相关特性,这次队列不用考虑在并发情况下的安全特性,并发包里面的数据结构就不用考虑了。于是我找啊找,到了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。

[置顶] ArrayDeque源码图析_第1张图片

    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);

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

     [置顶] ArrayDeque源码图析_第2张图片

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个元素此时,

[置顶] ArrayDeque源码图析_第3张图片

继续入队8会发生扩容:

[置顶] ArrayDeque源码图析_第4张图片

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

3.ArrayDeque 出队操作

我们执行如下操作

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

当出队的时候:

[置顶] ArrayDeque源码图析_第5张图片

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;
    }



 
 




你可能感兴趣的:([置顶] ArrayDeque源码图析)