Deque用法及原理讲解

Posted 叶长风

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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同时使用。

以上是关于Deque用法及原理讲解的主要内容,如果未能解决你的问题,请参考以下文章

deque双端队列用法

STL之deque用法

STL—deque使用及源码剖析

STL容器及适配器

C++ STL 之 deque

68)deque数组