Deqeue&ArrayDeque

本文介绍了Java中的双端队列Deque,它不仅具备队列的特性,还能作为栈来使用。ArrayDeque是Deque的一个实现,基于循环数组,线程不安全,且在需要时会按2的幂进行扩容。文章通过理论和代码测试详细讲解了ArrayDeque的工作原理和使用方法。

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

1.什么是Deqeue

Deque的含义是“double ended queue”,双端队列,传统队列操作受限FIFO,而这个队列可以双端进出,比较灵活;

它继承自Queue的(注意这儿是接口继承接口);

public interface Deque<E> extends Queue<E> {
	......
}

队列是一种特殊的线性容器,它是一种先进先出(FIFO)的数据结构。
它只允许在容器的头部进行删除操作,而在表的后端进行插入操作。进行插入操作的端成为队尾,进行删除操作的端称为队头。
当队列中没有元素时,被称为空队列。
同理当插入元素已经充满队列,被称为满队列.

再看下双端队列的Api:

可以看到扩展了不少功能,注意我划黄线的地方,可以看到它提供了一堆这种XXXfirst(),XXXlast()的方法。意思就是它不再是和队列一样,只能固定的FIFO的操作容器,而是既可以操作首,也可以操作尾,也可以LIFO;

而且它还有pop,push这样的方法,pop是哪儿的方法?看栈的Api:

意思就是说,依仗可以首尾操作的特性,它不仅仅可以作为一个队列来用,它还可以作为一个栈来用,并且官方也是建议用Dequeue替换Stack:

/**
 * ......
 * 更完整和一致的一组LIFO堆栈操作是由{@ Link Deque}接口及其实现提供应该优先使用这个类。
 * 
 * A more complete and consistent set of LIFO stack operations is
 * provided by the {@link Deque} interface and its implementations, which
 * should be used in preference to this class.  For example:
 * <pre>   
 * {@code
 *   Deque<Integer> stack = new ArrayDeque<Integer>();}</pre>
 *
 * @author  Jonathan Payne
 * @since   JDK1.0
 */
public
class Stack<E> extends Vector<E> {
      ......
}

小结下Deque的Api:它都是根据是否抛异常提供俩套api

然后对应总结下它和队列的api对比:

在了解了大概情况之后,我尝试去深入了解它,那么按照传统的姿势,去看他有哪些实现类,然后挑一个用途广泛的作为一个典型来入门。

Dequeue在java包下的默认子类都有:

我选择用ArrayQueue来作为研究对象。

2.ArrayDeqeue

1.大小自增长的队列
2.内部使用数组存储数据
3.线程不安全
4.内部数组长度为8、16、32…… 2的n次方
5.头指针head从内部数组的末尾开始,尾指针tail从0开始,在头部插入数据时,head减一,在尾部插入数据时,tail加一。当head==tail时说明数组的容量满足不了当前的情况,此时需要扩大容量为原来的二倍。

2.1.它的底层是循环数组:

啥是循环数组?
循环数组是实现循环队列的一种手段,与之对应的还有循环链表,这里的循环指的的是,当容器空间有富余的时候避免无谓的扩容,就比如这个ArrayDeque,一扩就是16位数组,那么究竟什么是循环数组呢?

假设我new了一个5位长度的数组,那么此刻如果我在继续往里放元素,就应该扩容了;

但此时我对head**“出队”**2个元素。此时head指针++2,于是就成了下图这样;

ok,黄色部分是空出来的富余内存部分,那么此时我再**“入队"一个元素,注意关键点来了,若是传统数组肯定就是扩容了,但若是循环数组,此时它的tail指针则是指向头部的富余内存的指针位置,避免了扩容,这就是所谓的"首尾相连""循环”**;

具体到ArrayDeque的源码就是下面这部分:

  /**
     * Inserts the specified element at the end of this deque.
     * <p>
     * <p>This method is equivalent to {@link #add}.
     *
     * @param e the element to add
     * @throws NullPointerException if the specified element is null
     */
    public void addLast(E e) {
        System.out.println("arrayDeque addLast == " + "tail == " + tail);
//        System.out.println("arrayDeque == "+"head == "+ head);
        if (e == null)
            throw new NullPointerException();
        elements[tail] = e;
        if ((tail = (tail + 1) & (elements.length - 1)) == head)//循环数组首尾相连
            doubleCapacity();
    }

内部维护一个长度为16的Object[]

    /**
     * Constructs an empty array deque with an initial capacity
     * sufficient to hold 16 elements.
     */
    public ArrayDeque() {
        elements = new Object[16];
    }

2.2.ArrayDeque代码测试:

下面我就结合上面的理论通过代码的方式来验证验证。

首先我复制一个ArrayDeque,目标是为了打log和调试数据,废话不多说直接看我新增的方法和Log:

  /**
     * 查看队列内部维护的数组的真实长度
     */
    public int sizeOfArray() {
        if (null != elements)
            return elements.length;
        return -1;
    }

    /**
     * 查看头尾指针位置
     */
    public void showHeadAndTail() {
        System.out.println("tail == " + tail);
        System.out.println("head == " + head);
    }

如何验证?
我们知道ArrayDeque默认16长度数组,那么我先入队16个元素,然后出队2个元素,然后观察它的头尾指针以及是否扩容。

public static void main(String[] args) {


        MyArrayDeque arrayDeque = new MyArrayDeque();

        //先入满队元素
        for (int i = 0; i < 15; i++) {
            arrayDeque.addLast("1");
        }

        //观察
        ob(arrayDeque);

        //出队俩个元素
        arrayDeque.poll();
        arrayDeque.poll();

        //观察
        ob(arrayDeque);

        //在入队2个元素
        arrayDeque.add("1");
        arrayDeque.add("1");

        //观察
        ob(arrayDeque);

        //再入队1个元素
        arrayDeque.add("1");

        //观察
        ob(arrayDeque);
    }

观察结果:

sizeOfArray == 16 //开始入满队元素时,内部数组长度为16,未扩容
tail == 15 //尾巴下标为15
head == 0  //头下标为默认,因为没出队

//此时出队了2个元素
sizeOfArray == 16 //内部数组容器依旧没扩容,也没变
tail == 15 //由于是出队所以尾部下标没变
head == 2  //由于是出队,所以理所应当的头部下标++2

//此时由入队1个元素
sizeOfArray == 16 //由于之前出队了2个元素,所以内存空间有富余,根据循环数组的特性,tail应该往头部head富余的地方走
tail == 1 //如上述
head == 2 //之前出队时候就是2

//此时在入队元素
sizeOfArray == 32 //由于队列已满,再入队时就是扩容了
tail == 16 //16就已经是扩容后的下标了
head == 0

3.本文Demo

https://github.com/zj614android/designPattern/tree/master/app/src/main/java/com/zj/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/java%E5%AE%B9%E5%99%A8

4.Thanks

https://www.jianshu.com/p/5763d9c1c321

帮我写出下列代码修正后的正确代码以及输出结果#include "stdio.h" #include "stdlib.h" #include "malloc.h" #include "string.h" #define MAXQSIZE 5 #define ERROR 0 #define OK 1 typedef struct {char *base; int front; int rear; int length; }hc_sqqueue; void main() {hc_sqqueue *initqueue_hc(); int cshqueue_hc(hc_sqqueue *q); int enqeue_hc(hc_sqqueue *q,char e); int deqeue_hc(hc_sqqueue *q); int printqueue_hc(hc_sqqueue *q); hc_sqqueue *q; char f,e; printf("建立队列(C)\n"); printf("初始化队列(N)\n"); printf("入队列元素(I)\n"); printf("出队列元素(D)\n"); printf("退出(E)\n\n"); do {printf("输入要做的操作:"); flushall(); f=getchar(); if(f=='C')q=initqueue_hc(); else if(f=='N') {cshqueue_hc(q);printqueue_hc(q);} else if(f=='I') {printf("输入要的入队的元素:"); flushall();e=getchar(); enqeue_hc(q,e);printqueue_hc(q);} else if(f=='D') {deqeue_hc(q);printqueue_hc(q);} }while(f!='E'); hc_sqqueue *initqueue_hc() {hc_sqqueue q; q=(hc_sqqueue)malloc(sizeof(hc_sqqueue)); if(!q)exit(ERROR); return(q);} int cshqueue_hc(hc_sqqueue q) {char e; int enqeue_hc(hc_sqqueue q,char e); q->base=(char)malloc(MAXQSIZEsizeof(char)); if(!q->base)exit(ERROR); q->front=q->rear=0;q->length=0; printf("输入元素以#结束:\n"); flushall(); e=getchar(); while(e!='#') {enqeue_hc(q,e); if(q->length==MAXQSIZE)return(ERROR); else {flushall();e=getchar();}} return(OK);} int enqeue_hc(hc_sqqueue *q,char e) {if(q->length==MAXQSIZE)return(ERROR); q->base[q->rear]=e; q->rear=(q->rear+1)%MAXQSIZE; q->length++; return(OK);} int deqeue_hc(hc_sqqueue *q) {if(q->length==0)return (ERROR); printf("出队的元素为:%c\n",q->base[q->front]); q->front=(q->front+1)%MAXQSIZE;q->length--; return (OK);} int printqueue_hc(hc_sqqueue *q) {int t=q->front; if(q->length==0){printf("队空!\n");return(ERROR);} if(q->length==MAXQSIZE)printf("队满!\n"); printf("当前队列中元素为:\n"); do{printf("%c\n",q->base[t]); t=(t+1)%MAXQSIZE;}while(t!=q->rear); return(OK);}
05-25
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值