1. 程式人生 > >Deque用法及原理講解

Deque用法及原理講解

Deque用法及原理講解


最近想著對現有知識點進行一個總結,決定從集合開始,想想便從Deque開始吧,Deque用的比較少,但是還是一個功能十分強大的佇列,這種雙向佇列即可以支援先進後出,也能支援先進先出的格式,相當於同時實現了Stack和Vector,今天就來講一講Deque用法以及底層原始碼。

1. Deque用法


先寫一個簡單的demo,這個demo也是以前查Deque時看別人寫的,然後對著寫了一遍,如下:

package cn.com.queue.deque;

import java.util.ArrayDeque;
import java.util.
Deque; /** * @author xiaxuan */ public class DequeTest { public static void main(String[] args) { Deque<Integer> mDeque = new ArrayDeque<>(); for (int i = 0; i < 5; i++) { mDeque.offer(i); } System.out.println(mDeque.peek()); System.
out.println("********集合方式遍歷*********"); //集合方式遍歷,元素不會被移除 for (Integer x : mDeque) { System.out.println(x); } System.out.println("********遍歷佇列***********"); //佇列方式遍歷,元素逐個被移除 while (mDeque.peek() != null) { System.out.println(mDeque.
poll()); } System.out.println("**********進棧操作*********"); mDeque.push(10); mDeque.push(15); mDeque.push(24); print(mDeque); System.out.println("*********出棧操作***********"); System.out.println(mDeque.pop()); } public static void print(Deque<Integer> queue) { //集合方式遍歷,元素不會被移除 for (Integer x : queue) { System.out.println(x); } } }

執行結果如下圖:
在這裡插入圖片描述
上圖中

2. Deque原理講解


首先我們看Deque的實現類ArrayQueue的資料結構,可以看到ArrayQueue還是使用陣列的結構,應該來說陣列是實現集合類的基礎資料結構。

    /**
     * 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; // non-private to simplify nested class access

現在看ArrayQueue的offer操作,原始碼如下:

/**
     * Inserts the specified element at the end of this deque.
     *
     * <p>This method is equivalent to {@link #offerLast}.
     *
     * @param e the element to add
     * @return {@code true} (as specified by {@link Queue#offer})
     * @throws NullPointerException if the specified element is null
     */
    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)
            throw new NullPointerException();
        elements[tail] = e;
        if ( (tail = (tail + 1) & (elements.length - 1)) ==       head)
            doubleCapacity();
    }

這裡的offer操作實際上就是把元素新增到陣列的頭部, 空間不夠了則再對空間進行擴容,擴容的操作就不講了,實際上就是陣列的copy。

然後我們再來看一下push操作,push是棧特有的方法,此處如果是先進先出的操作,那麼這裡應該就是講元素新增到陣列的第一個位置,然後後面的元素逐個後移,我們來看看push原始碼的實現。

// *** Stack methods ***

    /**
     * Pushes an element onto the stack represented by this deque.  In other
     * words, inserts the element at the front of this deque.
     *
     * <p>This method is equivalent to {@link #addFirst}.
     *
     * @param e the element to push
     * @throws NullPointerException if the specified element is null
     */
    public void push(E e) {
        addFirst(e);
    }

// The main insertion and extraction methods are addFirst,
    // addLast, pollFirst, pollLast. The other methods are defined in
    // terms of these.

    /**
     * Inserts the specified element at the front of this deque.
     *
     * @param e the element to add
     * @throws NullPointerException if the specified element is null
     */
    public void addFirst(E e) {
        if (e == null)
            throw new NullPointerException();
        elements[head = (head - 1) & (elements.length - 1)] = e;
        if (head == tail)
            doubleCapacity();
    }

這裡倒是有點意思,說明前面的思考確實是不對的,這裡用的是head、tail兩個指標,tail用來往頭部新增元素,head用來往陣列尾部新增元素,如果head == tail則進行擴容。

那再看看陣列的peak操作。

/**
     * Retrieves, but does not remove, the head of the queue represented by
     * this deque, or returns {@code null} if this deque is empty.
     *
     * <p>This method is equivalent to {@link #peekFirst}.
     *
     * @return the head of the queue represented by this deque, or
     *         {@code null} if this deque is empty
     */
    public E peek() {
        return peekFirst();
    }
    
   public E peekFirst() {
        // elements[head] is null if deque empty
        return (E) elements[head];
    }

peak就是彈出佇列的頭部元素,就是head指標指向的元素,這個比較比較容易理解。

現在再看下poll操作,poll每次操作元素時,會逐個移除佇列頭部元素。

/**
     * Retrieves and removes the head of the queue represented by this deque
     * (in other words, the first element of this deque), or returns
     * {@code null} if this deque is empty.
     *
     * <p>This method is equivalent to {@link #pollFirst}.
     *
     * @return the head of the queue represented by this deque, or
     *         {@code null} if this deque is empty
     */
    public E poll() {
        return pollFirst();
    }

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;     // Must null out slot
        head = (h + 1) & (elements.length - 1);
        return result;
    } 

這裡就是將頭部的元素取出並返回,然後將頭部的元素置為null,然後head值加一。

再來看看棧的pop操作,想必和poll類似。

/**
     * Pops an element from the stack represented by this deque.  In other
     * words, removes and returns the first element of this deque.
     *
     * <p>This method is equivalent to {@link #removeFirst()}.
     *
     * @return the element at the front of this deque (which is the top
     *         of the stack represented by this deque)
     * @throws NoSuchElementException {@inheritDoc}
     */
    public E pop() {
        return removeFirst();
    }

/**
     * @throws NoSuchElementException {@inheritDoc}
     */
    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;
        elements[h] = null;     // Must null out slot
        head = (h + 1) & (elements.length - 1);
        return result;
    }

pop的操作和poll方法實現是相同的,其實這個可以理解,因為queue和stack各自分別是先進先出與先進後出的模式,所以取資料都是一樣的。

3. 綜上


雙端佇列作為Queue和Stack的雙重實現,但是在使用的時候只能選擇一種使用,不能Queue與Stack的api同時使用。