ArrayDeque源码解析

news2024/11/29 12:40:03

ArrayDeque源码解析

问题

(1)什么是双端队列?

(2)ArrayDeque 是怎么实现双端队列的?

(3)ArrayDeque 是线程安全的吗?

(4)ArrayDeque 是有界的吗?

简介

  • 双端队列是一种特殊的队列,它的两端都可以进出元素,故而得名双端队列。
  • ArrayDeque 是一种以数组方式实现的双端队列,它是非线程安全的。

由其名字可以看出,其是一个由数组实现的双端队列,对比 LinkedList 是由链表实现的双端队列。

继承体系

image-20221123213019189

通过继承体系可以看,ArrayDeque 实现了 Deque 接口,Deque 接口继承自 Queue 接口,它是对 Queue 的一种增强。

public interface Deque<E> extends Queue<E> {
    // 添加元素到队列头
    void addFirst(E e);
    // 添加元素到队列尾
    void addLast(E e);
    // 添加元素到队列头
    boolean offerFirst(E e);
    // 添加元素到队列尾
    boolean offerLast(E e);
    // 从队列头移除元素
    E removeFirst();
    // 从队列尾移除元素
    E removeLast();
    // 从队列头移除元素
    E pollFirst();
    // 从队列尾移除元素
    E pollLast();
    // 查看队列头元素
    E getFirst();
    // 查看队列尾元素
    E getLast();
    // 查看队列头元素
    E peekFirst();
    // 查看队列尾元素
    E peekLast();
    // 从队列头向后遍历移除指定元素
    boolean removeFirstOccurrence(Object o);
    // 从队列尾向前遍历移除指定元素
    boolean removeLastOccurrence(Object o);
    /*
     * 队列中的方法
     */  
    // 添加元素,等于addLast(e)
    boolean add(E e);
     // 添加元素,等于offerLast(e)
    boolean offer(E e);
    // 移除元素,等于removeFirst()
    E remove();
    // 移除元素,等于pollFirst()
    E poll();
    // 查看元素,等于getFirst()
    E element();
    // 查看元素,等于peekFirst()
    E peek();
    /*
     * 栈方法
     */  
    // 入栈,等于addFirst(e)
    void push(E e);
    // 出栈,等于removeFirst()
    E pop();
	/*
     * Collection中的方法
     */  
    // 删除指定元素,等于removeFirstOccurrence(o)
    boolean remove(Object o);
    // 检查是否包含某个元素
    boolean contains(Object o);
    // 元素个数
    public int size();
    // 迭代器
    Iterator<E> iterator();
    // 反向迭代器
    Iterator<E> descendingIterator();
}

Deque 中新增了以下几类方法:

  • *First,表示从队列头操作元素;
  • *Last,表示从队列尾操作元素;
  • push(e)pop(),以的方式操作元素的方法;

Java 里有一个叫做 Stack 的类 ,却没有叫做 Queue 的类(它是个接口名字),但是 Stack 继承自 Vector,方法都是同步的,一般不使用。

当需要使用栈时,Java 已不推荐使用 Stack,而是推荐使用更高效的 ArrayDeque;既然 Queue 只是一个接口, 当需要使用队列时也就首选 ArrayDeque 了(次选是 LinkedList)。

源码解析

属性

    // 存储元素的数组
    transient Object[] elements;
    // 头指针
    transient int head;
    // 尾指针
    transient int tail;
    // 默认最小容量(注意:elements的长度一定是2的次方幂)
    private static final int MIN_INITIAL_CAPACITY = 8;

从属性我们可以看到,ArrayDeque 使用数组存储元素,并使用头尾指针标识队列的头和尾,其最小容量是 8。

ArrayDeque底层是使用数组实现的,而且数组的长度必须是2的整数次幂,这么操作的原因是为了后面位运算好操作。在ArrayDeque当中有两个整形变量headtail,分别指向右侧的第一个进入队列的数据和左侧第一个进入队列的数据,整个内存布局如下图所示:

image-20221124184634273

其中 tail 指的位置没有数据,head 指的位置存在数据。

构造方法

  • 调用无参构造器时,默认创建一个长度为16的数组。
  • 调用传入初始容量 n 的构造器,当 n 小于 8 时,会初始化一个长度为 8 的一个数组。
  • 当 n 大于等于 8 时,会初始化一个长度为<大于n的最小的2的幂>的数组(比如传入 3 算出来是 8,传入 9 算出来是 16,传入 16 算出来是 32)
    /*          
     * 空参构造器,底层初始化一个长度为16的数组
     */
	public ArrayDeque() {
        elements = new Object[16];
    }

	/*          
	 * 指定元素个数初始化
	 * 传入初始容量,注意最终的容量是大于(没有等于)numElements的最大的2的幂
	 * 然后会创建出来。
	 */
    public ArrayDeque(int numElements) {
        allocateElements(numElements);
    }
	
	/* 
	 * 传入一个集合,将集合c中的元素初始化到数组中
	 * 创建一个长度为<小于等于c.size的最大的2的幂>的数组
	 * 然后将c中的元素添加到elements中。
	 */
    public ArrayDeque(Collection<? extends E> c) {
        allocateElements(c.size());
        addAll(c);
    }

allocateElements()

 	// 构造一个长度为<严格大于numElements的最小的2的幂>的一个数组
	private void allocateElements(int numElements) {
        elements = new Object[calculateSize(numElements)];
   }

	// 返回严格大于numElements的最小的2的幂 (当numElements小于8时,返回8)
    private static int calculateSize(int numElements) {
        // MIN_INITIAL_CAPACITY = 8
        int initialCapacity = MIN_INITIAL_CAPACITY;
        // 当numElements大于等于8时,计算出大于numElements的最小的2的幂
        if (numElements >= initialCapacity) {
            initialCapacity = numElements;
            initialCapacity |= (initialCapacity >>>  1);
            initialCapacity |= (initialCapacity >>>  2);
            initialCapacity |= (initialCapacity >>>  4);
            initialCapacity |= (initialCapacity >>>  8);
            initialCapacity |= (initialCapacity >>> 16);
            initialCapacity++;
			// 条件成立:说明爆int了,需要缩小数据,将initialCapacity无符号右移一位,相当于/2
            if (initialCapacity < 0)   // Too many elements, must back off
                initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
        }
        // 这里如果numElements小于8时,直接返回8
        return initialCapacity;
    }

通过构造方法,我们知道默认初始容量是 16,最小容量是 8。

关于 calculateSize() 方法的图文分析可参考该文章:17张图带你深度剖析 ArrayDeque(JDK双端队列)源码

入队

  • 入队有很多方法,我们这里主要分析两个,addFirst(e)addLast(e)

addFirst(E, e)

    // 从队头入队
	public void addFirst(E e) {
        // 不允许null元素
        if (e == null)
            throw new NullPointerException();
        /*
         * 将head指针减1并与数组长度减1取模
         * 因为element.length一定是2的幂,2的幂-1的二进制从低位起是一串1,高位都是0
         * 初始时head = 0,0 - 1 = -1 ,-1 & 15 = 15,此时head = 15
         *      下一次 15 - 1 = 14,14 & 15 = 14,此时head = 14
         *      再下一次 14 - 1 = 13,13 & 15 = 13,此时head = 13
         *      ...
         * 这是为了防止数组到头了边界溢出。
         * 最终如果到头了,且数组未满时,就从尾再向前,相当于循环利用数组。
         * 即head指向的是当前队头元素。
         */
        elements[head = (head - 1) & (elements.length - 1)] = e;
        // tail指向的是头元素的下一个位置。判断head == tail即判断数组是否满了,需要扩容。
        if (head == tail)
            // 从方法名可以看出,扩容为原数组长度2倍。
            doubleCapacity();
    }

image-20221124205607261

addLast(E e)

    // 从队尾入队
    public void addLast(E e) {
        // 不允许null元素
        if (e == null)
            throw new NullPointerException();
        // 初始时tail为0,直接入队,此时tail指向的是从队尾入队队列的头元素的下一个位置。
        elements[tail] = e;
        /*
         * head指向的是队头元素的位置
         * tail + 1指向队头的下一个元素,判断是否 == head,即判断数组是否满了。
         * 即是否走扩容的逻辑。
         */
        if ( (tail = (tail + 1) & (elements.length - 1)) == head)
            doubleCapacity();
    }

image-20221124205801398

(1)入队有两种方式,从队列头或者从队列尾;

(2)如果容量不够了,直接扩大为两倍

(3)通过取模的方式让头尾指针在数组范围内循环;

(4)x & (len - 1) = x % len,使用 & 位运算的方式更快;

扩容

    private void doubleCapacity() {
        // assert:断言,判断head是否等于tail
        // 值为true时,程序从断言语句处继续执行
		// 值为false时,程序从断言语句处停止执行
        assert head == tail;
        // 头指针的位置
        int p = head;
        // 数组长度
        int n = elements.length;
        // 头指针离数组尾的距离
        int r = n - p; // number of elements to the right of p
        // 新长度为旧长度的两倍
        int newCapacity = n << 1;
        // 判断是否溢出
        if (newCapacity < 0)
            throw new IllegalStateException("Sorry, deque too big");
        // 新建新数组
        Object[] a = new Object[newCapacity];
        // 将旧数组head之后的元素拷贝到新数组中
        System.arraycopy(elements, p, a, 0, r);
        // 将旧数组下标0到head之间的元素拷贝到新数组中
        System.arraycopy(elements, 0, a, r, p);
        // 赋值为新数组
        elements = a;
        // head指向0,tail指向旧数组长度表示的位置
        head = 0;
        tail = n;
    }

扩容这里迁移元素可能有点绕,请看下面这张图来理解。

image-20221124204230645

出队

  • 出队同样有很多方法,我们主要看两个,pollFirst()pollLast()

pollFirst()

    // 从队列头出队
    public E pollFirst() {
        int h = head;
        @SuppressWarnings("unchecked")
        // 取队列头元素 (head指向的就是头元素)
        E result = (E) elements[h];
        // 如果队列为空,就返回null
        if (result == null)
            return null;
        // 将队列头置为空
        elements[h] = null;
        // 队列头指针右移一位
        head = (h + 1) & (elements.length - 1);
        // 返回出队的元素
        return result;
    }

image-20221124205621038

pollLast()

    // 从队列尾出队
    public E pollLast() {
        // 尾指针左移一位 因为通过addLast()我们可以知道,tail指向的是头元素的下一个位置
        int t = (tail - 1) & (elements.length - 1);
        @SuppressWarnings("unchecked")
        // 取当前尾指针处元素
        E result = (E) elements[t];
        // 如果队列为空返回null
        if (result == null)
            return null;
        // 将当前尾指针处置为空
        elements[t] = null;
        // tail指向新的尾指针处
        tail = t;
        // 返回出队的元素
        return result;
    }

image-20221124205809400

(1)出队有两种方式,从队列头或者从队列尾;

(2)通过取模的方式让头尾指针在数组范围内循环;

(3)出队之后没有缩容

小补充

  • add() 默认在队列尾部插入
  • remove() 默认从队列头部删除
    public boolean add(E e) {
        addLast(e);
        return true;
    }

	public E remove() {
        return removeFirst();
    }

栈操作

前面我们介绍 Deque 的时候说过,Deque 可以直接作为栈来使用,那么 ArrayDeque 是怎么实现的呢?

    // 入栈
    public void push(E e) {
        addFirst(e);
    }

    // 出栈
    public E pop() {
        // 底层调用的还是pollFirst()
        return removeFirst();
    }

是不是很简单,入栈出栈只要都操作队列头就可以了。

总结

(1)ArrayDeque 是采用数组方式实现的双端队列;

(2)ArrayDeque 的出队入队是通过头尾指针循环利用数组实现的;

(3)ArrayDeque 容量不足时是会扩容的,每次扩容容量增加 1 倍

(4)ArrayDeque 可以直接作为使用;

彩蛋

双端队列与双重队列?

  • 双端队列(Deque)是指队列的两端都可以进出元素的队列,里面存储的是实实在在的元素。
  • 双重队列(Dual Queue)是指一种队列有两种用途,里面的节点分为数据节点和非数据节点,它是 LinkedTransferQueue 使用的数据结构。



参考文章

  • 彤哥读源码_死磕 java集合之ArrayDeque源码分析
  • shstart7_ArrayDeque源码解析
  • Chang-LeHung_17张图带你深度剖析 ArrayDeque(JDK双端队列)源码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/85150.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【正点原子FPGA连载】 第三十五章双目OV5640摄像头HDMI显示实验 摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

1&#xff09;实验平台&#xff1a;正点原子MPSoC开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id692450874670 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第三十五章双目O…

基于jsp+mysql+ssm二手书交易管理系统-计算机毕业设计

项目介绍 这样一个二手书交易网站为用户提供了一个可以在网上买卖图书的平台&#xff0c;用户可以通过二手书交易管理系统进行注册或登录操作&#xff0c;登录成功后可以查看自己已发布的售书信息或者求购信息。同时&#xff0c;用户可以浏览其他用户发布的售书信息和求购信息…

基于51单片机的多层电梯(1-16层)运行系统仿真设计_层数可改

基于51单片机的多层电梯(1-16层)运行系统仿真设计_层数可改 仿真图proteus 8.9 程序编译器&#xff1a;keil 4/5 编程语言&#xff1a;C语言 设计编号&#xff1a;S0027 视频演示 基于51单片机的多层电梯(1-16层)运行系统仿真设计演示视频主要功能&#xff1a; 结合实际情…

[附源码]Python计算机毕业设计SSM基于web的学生社团管理系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

[附源码]Python计算机毕业设计SSM基于的二手房交易系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 Ma…

基于Java+SQL Server2008开发的(WinForm)个人财物管理系统【100010036】

一、需求分析 个人财务管理系统是智能化简单化个人管理的重要的组成部分。并且随着计算机技术的飞速发展&#xff0c;计算机在管理方面应用的旁及&#xff0c;利用计算机来实现个人财务管理势在必行。本文首先介绍了个人财务管理系统的开发目的&#xff0c;其次对个人财务管理…

2022年12月中国数据库排行榜:OceanBase立足创新登榜首,华为腾讯排名上升树雄心

不经一番寒彻骨&#xff0c;怎得梅花扑鼻香。 2022年12月的 墨天轮中国数据库流行度排行榜 火热出炉&#xff0c;本月共有249个数据库参与排名&#xff0c;相比上月新增3个数据库。本月排行榜前十用一句话可以概括为&#xff1a;榜单前十一片红&#xff0c;TODO 格局重洗牌&…

[附源码]Python计算机毕业设计SSM基于web技术的米其林轮胎管理系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

SpringBoot中使用Spring-Retry重试框架 - 第454篇

悟纤&#xff1a;最近我看到自己之前的try/catch、while代码进行请求的重试&#xff0c;看着很不舒服。 师傅&#xff1a;确实了&#xff0c;为师以前也是写出过这样的一堆难看的代码。 悟纤&#xff1a;那师傅这个事情有解吗&#xff1f; ​师傅&#xff1a;徒儿&#xff0c;…

博客网页制作基础大二dw作业 web课程设计网页制作 个人网页设计与实现 我的个人博客网页开发

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

2022全年度平板电视十大热门品牌销量榜单

随着社会的发展&#xff0c;近年来&#xff0c;平板电视成为了彩电转型升级的新方向。随着我国传统彩电进入存量竞争阶段&#xff0c;平板电视已成为我国彩电行业结构调整和转型升级的主要方向。 根据鲸参谋数据统计&#xff0c;今年京东平台平板电视的年度累计销量达到1300多万…

< 在element-ui中: 使用el-tree + el-table组件,联动请求用户数据表格组件 (基础版,后续可能更新) >

文章目录&#x1f449; 前言&#x1f449; 一、效果演示&#x1f449; 二、原理&#x1f449; 三、实现代码往期内容 &#x1f4a8;&#x1f449; 前言 在 Vue elementUi 开发中&#xff0c;实现通过树状组织机构&#xff0c;点击查询用户信息联动效果&#xff01; 组件较为简…

MySQL数据库学习(6)

一、MySQL索引简介 索引是一种特殊的数据库结构&#xff0c;由数据表中的一列或多列组合而成&#xff0c;可以用来快速查询数据表中有某一特定值的记录。 通过索引&#xff0c;查询数据时不用读完记录的所有信息&#xff0c;而只是查询索引列。不然的话&#xff0c;数据库系统将…

强化学习_06_pytorch-doubleDQN实践(Pendulum-v1)

环境描述 环境是倒立摆&#xff08;Inverted Pendulum&#xff09;&#xff0c;该环境下有一个处于随机位置的倒立摆。环境的状态包括倒立摆角度的正弦值&#xff0c;余弦值&#xff0c;角速度&#xff1b;动作为对倒立摆施加的力矩(action Box(-2.0, 2.0, (1,), float32))。…

windows11安装cuda+cudnn

安装Nvidia显卡驱动 如需安装显卡驱动&#xff0c;在官方驱动下载网站找到自己的显卡型号对应的驱动下载并安装:官方驱动 | NVIDIA 安装CUDA 前言 windows10 版本安装 CUDA &#xff0c;首先需要下载两个安装包 CUDA toolkit&#xff08;toolkit就是指工具包&#xff09;cu…

Qt扫盲-QLineEdit理论总结

QLineEdit理论总结1. 简述2. 输入模式3. 输入限制4. 文本操作槽函数3. 信号4. 快捷键5. 外观1. 简述 QLineEdit 是一个有用的编辑功能类&#xff0c;主要是处理输入和编辑单行纯文本 &#xff0c;主要是单行哦&#xff0c;就用来输入简单&#xff0c;短小的字符串。内部其实已…

极客时间Kafka - 09 Kafka Java Consumer 多线程开发实例

文章目录1. Kafka Java Consumer 设计原理2. 多线程方案3. 代码实现4. 问题思考目前&#xff0c;计算机的硬件条件已经大大改善&#xff0c;即使是在普通的笔记本电脑上&#xff0c;多核都已经是标配了&#xff0c;更不用说专业的服务器了。如果跑在强劲服务器机器上的应用程序…

JSP ssh科研管理系统myeclipse开发mysql数据库MVC模式java编程计算机网页设计

一、源码特点 JSP ssh科研管理系统是一套完善的web设计系统&#xff08;系统采用ssh框架进行设计开发&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myec…

Core Scheduling

Core Scheduling要解决什么问题&#xff1f; core scheduling是v5.14中新增的功能&#xff0c;下图是内核数据结构为该功能所添加的字段。 为什么有core scheduling呢&#xff1f;因为当开启超线程(HyperThreading)时&#xff0c;一个物理核就变成了两个逻辑核&#xff0c;但&…

postgres 源码解析43 元组的插入流程详解 heap_insert

本文讲解postgres中元组的插入流程&#xff0c;深入了解其实现原理。同时此过程涉及元组xmin/xmax与标识位的设置细节&#xff0c;与事务的可见性部分密切相关相关&#xff0c;借此复习一下。 heappage结构 执行流程框架图 heap_prepare_insert 该函数执行内容较为简单&#…