Deque用法及原理讲解

本文详细解析了Deque的用法及原理,包括其双向队列特性,如何实现先进先出和后进先出,以及底层源码分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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同时使用。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值