Java数据结构之Deque

article/2025/10/4 3:07:11

Java数据结构之Deque

  • 引题
  • Deque接口分析
    • Deque的注释
    • 与Queue的联系
    • 还在使用Stack?你OUT啦!
    • peek方法更方便
    • 与List的不同
    • 与null说goodbye
  • 子类ArrayDeque.class分析
    • 基本结构
      • 官方的代码
      • 图解数据存储过程
    • 简单思考 1
    • 部分代码的分析
      • 关于初始容量
      • 关于扩容
      • 代码中的&运算
    • 简单思考2
    • 线程并不安全

引题

进入本文之前,我们先看一段代码

 Deque<Integer> que = new ArrayDeque<>();

上面的代码是否感觉到熟悉?前面的接口Deque有没有感觉与Queue–队列看起来有点像,都是que结尾。后面的ArrayDeque看着眼熟?是不是跟ArrayList有点像。但这个接口描述的到底是怎样的数据结构呢?我们现在就去源码里一探究竟吧。

Deque接口分析

我们按住ctrl+左键点进去看看Deque接口的源码就能明白这个类的作用是什么了?

Deque的注释

先看接口上面的注释,这里就不放英文了,我直接上一段百度翻译的原文。

支持两端元素插入和移除的线性集合。
deque是“双端队列”的缩写,通常发音为“deck”。
大多数Deque实现对它们可能包含的元素数量没有固定的限制,
但是这个接口支持容量限制的Deque以及那些没有固定大小限制的Deque。这个接口定义了访问deque容器两端元素的方法。
提供了插入、删除和检查元素的方法。
这些方法以两种形式存在:一个在操作失败时抛出异常,
另一个返回特殊值(null或false,取决于操作)。
插入操作的后一种形式是专门为使用容量受限的Deque实现而设计的;
在大多数实现中,插入操作不会失败。

抓住关键词:支持两端元素插入和移除的线性集合双端队列包含的元素数量没有固定的限制访问deque容器两端元素的方法

然后接着往下看注释:
在这里插入图片描述
表示该接口中的12个方法,其实从方法名称中我们就可以看出,这个数据结构可以从两端进行插入、取出、删除数据

与Queue的联系

该接口扩展了Queue接口。
当deque被用作队列时,会产生FIFO (First-In-First-Out)行为。
在deque容器的末尾添加元素,并从开头删除元素。

也就是说DequeQueue子类,可以把它当作队列来使用。
但还记得上面的关键词吗?支持两端元素插入和移除的线性集合
也就是Deque可以在两边都操作元素:新增、删除、访问元素等。
Queque 只能在队首 对元素进行操作。

还在使用Stack?你OUT啦!

Deque也可以用作LIFO(后进先出)堆栈。
这个接口应该优先于旧的Stack类使用。
当一个队列被用作堆栈时,元素从队列的开始被推入和弹出
。堆栈方法完全等同于Deque方法。

所以以后需要使用Stack的情况时候,要记得优先使用Deque哦。

peek方法更方便

注意,当一个deque用作队列或堆栈时,peek方法同样工作良好;
在这两种情况下,元素都是从队列的开头开始绘制的。

虽然有get方法获取元素,但官方表示:peek方法,你值得拥有!

与List的不同

与List接口不同,该接口不支持对元素的索引访问。

与null说goodbye

虽然不严格要求Deque实现禁止空元素的插入,但是强烈建议这样做。
强烈建议任何允许null元素的Deque实现的用户不要利用插入null的能力。
这是因为null被各种方法用作特殊的返回值,以指示deque为空。

因为null被各种方法作为特殊的返回值,所以建议不要存储null值。

子类ArrayDeque.class分析

Deque接口的实现类很多,这里就不一一分析了,以下只分析其中的的一个子类ArrayDeque

基本结构

官方的代码

    /*** 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/*** The index of the element at the head of the deque (which is the* element that would be removed by remove() or pop()); or an* arbitrary number equal to tail if the deque is empty.*/transient int head;/*** The index at which the next element would be added to the tail* of the deque (via addLast(E), add(E), or push(E)).*/transient int tail;/*** The minimum capacity that we'll use for a newly created deque.* Must be a power of 2.*/private static final int MIN_INITIAL_CAPACITY = 8;

上述代码的注释中已经将各个属性的作用描述的很清楚了,这里就直接翻译下。

/*存储deque容器元素的数组。deque的容量是数组的长度,总是2的幂。数组永远不允许被填满,除非在一个addX方法中,数组在被填满后立即被调整大小(参见doubleCapacity),从而避免头尾绕成相等。我们还保证所有不包含deque元素的数组单元格始终为空。
*/transient Object[] elements;//非私有以简化嵌套类访问/*deque容器头部元素的索引(即将被remove()或pop()移除的元素);或者如果deque为空,则等于tail的任意数。 -- elements的头部
*/transient int head;
/*下一个元素将被添加到deque容器尾部的索引(通过addLast(E), add(E),或push(E))。 -- 可以理解为elements的末端
*/transient int tail;/*我们将用于新创建deque的最小容量。一定是2的幂。 最小的初始化容量*/private static final int MIN_INITIAL_CAPACITY = 8;

值得注意的是transient 关键字是指,该数据只能在内存中使用,而无法保存到本地的文件中,这文章比较详细:Java中transient关键字的详细总结.

图解数据存储过程

从下图初始化的Deque结构中很明显可以看出为什么我们不能插入null。因为实例后各个元素的默认值就是null。从而插入null的话会造成以下情况:下次在该位置插入元素时无法确定该位置是否存在元素
在这里插入图片描述
从尾部插入两个元素,注意头(head)尾(tail)指针的位置
在这里插入图片描述
如果从头部插入数据,注意头(head)尾(tail)指针的位置
在这里插入图片描述
注意head和tail指向的地方是否有数据存储

简单思考 1

如果先后从头尾分别插入一个数据,请问插入第几个数据时扩容假设当head = tail 时扩容)?答案可以评论区留言哦。

部分代码的分析

关于初始容量

初始容量默认为8,如果小于8则设为8,大于8则取大于该数的2次幂的整数.

private void allocateElements(int numElements) {int initialCapacity = MIN_INITIAL_CAPACITY;//默认最小为8 如果大于8则取大于该数的2次幂的整数if (numElements >= initialCapacity) {initialCapacity = numElements;// |= 做一个向右移位后的或运算// >>>n 无符号右移n位 高位用0补齐// |= 就是 initialCapacity  = initialCapacity |  (initialCapacity >>>  1)initialCapacity |= (initialCapacity >>>  1);initialCapacity |= (initialCapacity >>>  2);initialCapacity |= (initialCapacity >>>  4);initialCapacity |= (initialCapacity >>>  8);initialCapacity |= (initialCapacity >>> 16);initialCapacity++;if (initialCapacity < 0) initialCapacity >>>= 1;//直接给你分配 2 ^ 30 容量的元素 一般都直接爆炸}//小于8直接设置为8elements = new Object[initialCapacity];}

关于扩容

直接扩展为双倍容量

    private void doubleCapacity() {assert head == tail;int p = head;int n = elements.length;int r = n - p;int newCapacity = n << 1;//直接移位变成两倍if (newCapacity < 0)throw new IllegalStateException("Sorry, deque too big");Object[] a = new Object[newCapacity]; //新的对象数组System.arraycopy(elements, p, a, 0, r);//写入 p - n 位置的数据到新的对象数组中(不包含第n号元素)System.arraycopy(elements, 0, a, r, p); //写入 0 - p 位置的数据到新数组中 (不包含第p号元素)elements = a;//替换对象数组的地址head = 0;tail = n;}

System.arraycopy(elements, 0, a, r, p);
这一行代码的意思就是:从elements数组的p号位置开始复制r个元素(包含p),到a数组中,从0开始。

代码中的&运算

以在队列首部增加元素方法addFirst为例,源码如下

public void addFirst(E e) {if (e == null)throw new NullPointerException();//先计算head 再存值elements[head = (head - 1) & (elements.length - 1)] = e;if (head == tail)//扩容doubleCapacity();
}

注意以下代码

 elements[head = (head - 1) & (elements.length - 1)] = e;//上面的代码可以看成以下两行head = (head - 1) & (elements.length - 1);elements[head] = e;

上述代码作用:将head往前移动一位,用来确定该插入元素存放的位置

head =  (head - 1) & (elements.length - 1)

上述的(&)操作,可以理解为一个取余
与取余不同的是当head - 1的结果为负数时,如当head = 0时,上面的赋值语句为:head = -1 & (elements.length - 1),结果是-1。
而&运算的结果是:head = (elements.length - 1)
即&操作,使得head的取值永远在[0 , elements.length - 1]中
而且&操作由于是二进制操作,所以相较于取余运行速度更快,效率更高

与之相对的addLast方法

public void addLast(E e) {if (e == null)throw new NullPointerException();//先存值 elements[tail] = e;//再计算tailif ( (tail = (tail + 1) & (elements.length - 1)) == head)//扩容doubleCapacity();
}

从这里我们很明显可以看出,两个方法的区别在于:

addFirst是先计算出head的位置,然后存放元素,head指向的地方是存储了元素的

而addLast是先存储元素,再计算tail的值,tail指向的地方为null

简单思考2

如果将addLast方法的执行顺序改为:先计算tail值,再存储元素。按照简单思考 1,请问插入第几个元素时扩容?并且扩容时数组内部是否有元素为null?答案可以评论区留言哦。

线程并不安全

我们可以看到,ArrayDeque中的方法都没有加synchronized的关键字,故而其是线程不安全的。


http://chatgpt.dhexx.cn/article/XBOIGy7S.shtml

相关文章

java关于Deque的使用

定义 双向队列&#xff1a;支持插入删除元素的线性集合。 java官方文档推荐用deque实现栈&#xff08;stack&#xff09;。 和Queue的区别 Deque是double ended queue&#xff0c;将其理解成双端结束的队列&#xff0c;双端队列&#xff0c;可以在首尾插入或删除元素。 Queue的…

【C++】deque的用法

目录 一、容器适配器二、deque的介绍三、deque的使用及缺陷1、deque的构造函数2、deque的元素访问接口3、deque的 iterator的使用4、deque的增删查改4、deque的缺陷5、为什么选择deque作为stack和queue的底层默认容器 一、容器适配器 在了解deque前&#xff0c;我们先讲一讲什…

Python deque的用法介绍

Python deque的用法介绍 deque 是Python标准库 collections 中的一个类&#xff0c;实现了两端都可以操作的队列&#xff0c;相当于双端队列&#xff0c;与Python的基本数据类型列表很相似。 Python实现双端队列参考&#xff1a;https://blog.csdn.net/weixin_43790276/artic…

C++ deque的用法与示例

C deque的用法与示例 deque容器的介绍 Vector 容器是单向开口的连续内存空间&#xff0c;deque 则是一种双向开口的连续线性空间。所谓的双向开口&#xff0c;意思是可以在头尾两端分别做元素的插入和删除操作&#xff0c;当然&#xff0c;vector 容器也可以在头尾两端插入元…

deque用法详解

“无意中发现了一个巨牛的人工智能教程&#xff0c;忍不住分享一下给大家。教程不仅是零基础&#xff0c;通俗易懂&#xff0c;而且非常风趣幽默&#xff0c;像看小说一样&#xff01;觉得太牛了&#xff0c;所以分享给大家。点这里可以跳转到教程。” deque函数&#xff1a; …

deque容器详解

文章目录 1. deque容器基本概念2. deque构造函数3. deque赋值操作4. deque大小操作5. deque插入和删除6. deque数据存取7. deque排序 1. deque容器基本概念 功能&#xff1a; 双端数组&#xff0c;可以对头端进行插入删除操作 deque与vector区别&#xff1a; vector对于头部…

双端队列(Deque)

一、简介 deque&#xff0c;即双端队列(double ended queue)&#xff0c;是一种可以在两端扩展或收缩的序列化容器。 deque是C 标准模板库的一部分&#xff0c;想要使用deque&#xff0c;需要在程序中包含头文件deque。 #include<deque>二、定义和初始化 1.格式&#x…

关于Deque的详解

1. 定义 双向队列&#xff1a;支持插入删除元素的线性集合&#xff1b;java官方文档推荐用deque实现栈&#xff08;stack&#xff09;。 2. 和Queue的区别 Deque是double ended queue&#xff0c;将其理解成双端结束的队列&#xff0c;双端队列&#xff0c;可以在首尾插入或删除…

deque用法深度解析,一篇文章弄懂deque容器各种操作

&#x1f4cb; 前言 &#x1f5b1; 博客主页&#xff1a;在下马农的碎碎念✍ 本文由在下马农原创&#xff0c;首发于CSDN&#x1f4c6; 首发时间&#xff1a;2022/01/11&#x1f4c5; 最近更新时间&#xff1a;2022/01/11&#x1f935; 此马非凡马&#xff0c;房星本是星。向前…

未禁用nouveau导致Ubuntu安装Cuda的runfile安装方法出错:[ERROR]: Install of 455.32.00 failed, quitting

很多朋友在给Ubuntu&#xff08;Linux&#xff09;安装Cuda时&#xff0c;参考官方安装步骤导致安装出错&#xff1a; wget https://developer.download.nvidia.com/compute/cuda/11.1.1/local_installers/cuda_11.1.1_455.32.00_linux.run sudo sh cuda_11.1.1_455.32.00_lin…

Ubuntu 安装 NVIDIA 显卡驱动详细步骤(ERROR: The Nouveau kernel driver is currently in use by your system)

1. 禁用 Nouveau 驱动 在禁用 Nouveau 驱动前我们先了解下它是啥&#xff1f;有什么作用。 Nouveau 是由第三方为 NVIDIA 显卡开发的一个开源 3D 驱动&#xff0c;也没能得到 NVIDIA 的认可与支持。虽然 Nouveau Gallium3D 在游戏速度上还远远无法和 NVIDIA 官方私有驱动相提…

Ubuntu 18.04 安装 nvidia 显卡驱动 离线安装 禁用 nouveau

Ubuntu 18.04 安装 nvidia 显卡驱动 离线安装 1 系统2 查看显卡2.1 更新 pci.ids 文件 3 安装显卡驱动 510.543.1 安装 nvtop 4 禁用 nouveau5 安装 cuda 11.6.15.1 设置环境变量 1 系统 # lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description:…

[Ubuntu]:禁用nouveau、安装卸载NVIDIA CUDA及驱动(深度学习)

这里只针对Ubuntu的安装卸载&#xff0c;安装驱动方式的不同&#xff0c;卸载也有些许不同。通常有3种方式&#xff1a; 通过apt包管理工具来安装&#xff0c; 这样的方式好处是卸载安装的管理跟其他软件一致 通过.deb包来安装&#xff0c;这里就跳过apt&#xff0c;直接使用了…

ubuntu18.04 禁用自带nouveau后重启无法进入系统

问题 按照链接步骤&#xff1a;禁用ubuntu 自带显卡驱动Nouveau&#xff0c; 又按照推荐安装驱动&#xff1a;$ sudo ubuntu-drivers autoinstall&#xff0c;重启电脑后&#xff0c;无法进入系统&#xff0c;一直重复停留在密码输入界面。 解决方法&#xff1a; 开机进入ubun…

Ubuntu20.04(18.04通用)禁用nouveau,安装NVIDIA显卡驱动

卸载其它版本NVIDIA驱动 sudo apt-get --purge remove nvidia*sudo apt autoremove禁掉nouveau 打开文本形式 sudo gedit /etc/modprobe.d/blacklist.conf 或直接终端打开形式 sudo vim /etc/modprobe.d/blacklist.conf sudo vi /etc/modprobe.d/blacklist.conf 在打开的文件…

已解决:ubuntu18.4禁用nouveau驱动(如何关闭secure boot)

在安装显卡驱动等过程中往往会需要禁用bios中的secure boot&#xff0c;因为secure boot会阻止第三方源安装驱动&#xff0c;只要保证安装源可靠&#xff0c;禁用并不会带来多大隐患。 1.开机在未亮屏之前反复按F2进入设置界面 2.取消勾选后退出重启 完成bios中的secure boot禁…

Ubuntu18.04禁用nouveau驱动,安装NVIDIA显卡驱动。

一、关闭secure boot&#xff0c;禁用nouveau驱动。 1.禁用bios中的secure boot&#xff0c;因为secure boot会阻止第三方源安装的驱动&#xff0c;禁用不会带来多大隐患。 2.禁用nouveau驱动&#xff0c;这是Ubuntu默认的开源显卡驱动&#xff0c;与N卡驱动一起使用会导致兼…

openEuler操作系统禁用 Nouveau

目录 一、什么是openEuler 二、什么是Nouveau 三、禁用Nouveau Liunx系统安装NVIDIA显卡驱动时需要禁用Nouveau&#xff0c;openEuler操作系统也不例外&#xff0c;但是网上openEuler操作系统如何禁用Nouveau的资料比较少&#xff0c;而且基本都不靠谱&#xff0c;我找到一个…

LWN:NVIDIA 与 nouveau!

关注了就能看到更多这么棒的文章哦&#xff5e; NVIDIA and nouveau By Jake EdgeOctober 5, 2022LPCDeepL assisted translationhttps://lwn.net/Articles/910343/ 英伟达图形加速硬件的源代码发不了&#xff0c;对人们来说这也许是一个惊喜&#xff1b;至少快速浏览下来&…

干掉Nouveau安装Linux Nvidia显卡驱动

干掉Nouveau安装Linux Nvidia显卡驱动 首先说明下什么是Nouveau&#xff0c;为什么有些系统安装N卡驱动的时候会提示“ERROR: The Nouveau kernel driver is currently in use by your system. This driver is incompatible with the NVIDIA driver……”之类的错误。 Nouvea…