一、什么是ArrayDeque

1、Deque与Queue

了解这个之前,我们要先知道什么是Deque,它和Queue有什么区别:在java中,Queue被定义成单端队列使用,Deque被定义成双端队列 即Queue可以访问两端但是只能修改队头,而Deque可以访问两端并且可以在队首和队尾删除和插入元素。
基于Deque的特性,ArrayDeque即可以作为Queue来使用也可以作为栈来使用,而且可以决定队列那边受限或者栈哪边进出。

2、ArrayDeque的构造器

public ArrayDeque() {elements = new Object[16];
}public ArrayDeque(int numElements) {allocateElements(numElements);//调用allocateElements方法
}private void allocateElements(int numElements) {//初始化elements元数据数组,长度由calculateSize方法决定elements = new Object[calculateSize(numElements)];
}// 找到大于需要长度的最小的2的幂整数,至少要大于8
private void allocateElements(int numElements) {// MIN_INITIAL_CAPACITY为8int initialCapacity = MIN_INITIAL_CAPACITY;// 假设用户输入的为9if (numElements >= initialCapacity) {// initialCapacity = 9; initialCapacity = numElements;// initialCapacity = 9 | ( 9 >>> 1)// initialCapacity = ( 1001 ) | ( 0100 ) = 1101 = 13;initialCapacity |= (initialCapacity >>>  1);// initialCapacity = 13 | ( 13 >>> 2)// initialCapacity = ( 1101 ) | ( 0011 ) = 1111 = 15;initialCapacity |= (initialCapacity >>>  2);// initialCapacity = 15 | ( 15 >>> 4)// initialCapacity = ( 1111 ) | ( 0000 ) = 1111 = 15;initialCapacity |= (initialCapacity >>>  4);// initialCapacity = 15 | ( 15 >>> 8)// initialCapacity = ( 1111 ) | ( 0000 ) = 1111 = 15;initialCapacity |= (initialCapacity >>>  8);initialCapacity |= (initialCapacity >>> 16);// 15+1 = 16;initialCapacity++;if (initialCapacity < 0)    // 超过int的上限了,即符号位为1,其它位全为0initialCapacity >>>= 1; // 设置为2^30次方}// 创建数组(数组的长度为:大于需要长度的最小的2的幂整数)elements = new Object[initialCapacity];
}

以上两个构造方法实现中:
第一个构造方法,创建一个默认长度为8的数组;

第二个构造方法,如代码中注释举例,其会通过allocateElements(numElements)将数组的长度定义为2的幂(找到大于需要长度的最小的2的幂整数);
allocateElements(numElements)方法:可以将一个任意的初始值转化为2^n的值。
如果本身传进来的值就是2^n 的值,那么经过转化会变成2^(n+1);
如果传入的值大于等于2^30,那么经过转化会变成负值,即< 0,此时会把初始值设置为2^30, 即最大的容量只有2^30;

补码变1原理

我们了解到要找到大于或等于整数 numElements 的最小的 2 的幂可以先把最高位及以下的所有低位「变」成 1,然后再加 1
一个问题就是如果这个数刚好是2的整数幂,那么这么操作后就会变为原数的两倍,但对于ArrayDequq,刚好满足要存的数显然是不行的,所以避免后面的扩容操作,不如在初始化的时候就设置为两倍大小。
如上面的例子,

  • initialCapacity |= (initialCapacity >>> 1); 是将最高位右移1位使得最高位和次高位设置为1
  • initialCapacity |= (initialCapacity >>> 2);在上一步的基础上,将最高位和次高位右移两位,使得前四位都为1,后面的步骤类似
  • 一直到initialCapacity |= (initialCapacity >>> 16);操作后如果最高位在31位(因为如果32位为1那么就是负数,则会在最开始就设置为8)那么所有位都会变为1(除符号位)
  • 执行加1操作,得到大于等于设置值得最小2的幂,然后判断是否小于0
  • 小于0说明越界,即int无法存储这个数,此时我们得到的数应该是符号位位1,其它位为0的数,右移一位将数组容量设置为最大值2^30

二、ArrayDeque对数据的操作

Queue 方法 等效的Deque方法 说明
add(e) addLast(e) 向队尾插入元素,失败则抛出异常
offer(e) offerLast(e) 向队尾插入元素,失败则返回false
remove() removeFirst() 获取并删除队首元素,失败则抛出异常
poll() pollFirst() 获取并删除队首元素,失败则返回null
element() getFirst() 获取但不删除队首元素,失败则抛出异常
peek() peekFirst() 获取但不删除队首元素,失败则返回null

注:因为Deque是Queue的实现类,所以以上12个方法Deque都有

Stack 方法 等效的Deque方法 说明
push(e) addFirst(e) 向栈顶插入元素,失败则抛出异常
offerFirst(e) 向栈顶插入元素,失败则返回false
pop() removeFirst() 获取并删除栈顶元素,失败则抛出异常
pollFirst() 获取并删除栈顶元素,失败则返回null
peek() peekFirst() 获取但不删除栈顶元素,失败则抛出异常
peekFirst() 获取但不删除栈顶元素,失败则返回null

上面两个表共定义了Deque的12个接口。添加,删除,取值都有两套接口,它们功能相同,区别是对失败情况的处理不同。一套接口遇到失败就会抛出异常,另一套遇到失败会返回特殊值(false或null)。除非某种实现对容量有限制,大多数情况下,添加操作是不会失败的。 虽然Deque的接口有12个之多,但无非就是对容器的两端进行操作,或添加,或删除,或查看。明白了这一点讲解起来就会非常简单。
摘自链接: Java ArrayDeque源码剖析.

三、ArrayDeque的底层实现

首先要知道ArrayDeque底层为数组,那么数组如何实现双端队列呢?

1、ArrayDeque成员变量

transient Object[] elements; //元数据数组
transient int head;//队列头部所在位置
transient int tail;//尾部所在位置

至于为何要给element数组使用transient修饰,原因和ArrayList一样,就是防止扩容后网络传输没用的数据影响效率。

2、ArrayDeque双端队列实现原理

ArrayDeque底层通过数组实现,为了满足可以同时在数组两端插入或删除元素的需求,该数组还必须是循环的,即循环数组(circular array),也就是说数组的任何一点都可能被看作起点或者终点。

如何实现循环数组?

在队头插入数据源码:

// 在队列前边 添加元素
public void addFirst(E e) {// 存入空数据时,抛出异常NullPointerExceptionif (e == null)throw new NullPointerException();elements[head = (head - 1) & (elements.length - 1)] = e;// 空间不足if (head == tail)doubleCapacity(); // 扩容
}

head = head - 1即,将新数据赋值给head的前一个位置,所以head指向的是队头的第一个元素。

下标越界的处理解决起来非常简单,head = (head - 1) & (elements.length - 1)就可以了,这段代码相当于取余,同时解决了head为负值的情况。因为elements.length必需是2的指数倍,elements - 1就是二进制低位全1,跟head - 1相与之后就起到了取模的作用,如果head - 1为负数(其实只可能是-1),则相当于对其取相对于elements.length的补码。

在队尾插入数据源码

public void addLast(E e) {if (e == null)throw new NullPointerException();elements[tail] = e;if ( (tail = (tail + 1) & (elements.length - 1)) == head)doubleCapacity();
}

首先进行赋值,可以了解到tail指向的是队尾的第一个可以插入元素的空位,然后再将tail向后移一位。下标越界与head一样。

判断队满与扩容

当head和tail指向同一个位置时表示队满,调用doubleCapacity();方法,将容量扩大为原来的两倍。

private void doubleCapacity() {assert head == tail;int p = head;int n = elements.length;int r = n - p; // number of elements to the right of pint 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);System.arraycopy(elements, 0, a, r, p);elements = a;head = 0;tail = n;
}

首先复制head右边的数据,再复制head左边的数据,然后设置head为0,tail为原数组长度。

四、ArrayDeque与LinkedList的比较

ArrayDeque和LinkedList都实现了Deque,都能作为双端队列使用,但是ArrayDeque为变长数组,有要扩容的问题
LinkedList使用Node来存储数据,数据的插入伴随着对象的创建,使得插入操作速度较慢,占用空间大
一般来说,数据量少的建议使用LinkedList,数据量大的建议使用ArrayDeque。

ArrayDeque的缺点:

  • ①不能存储null
  • ②线程不安全,LinkedList可以通过Collections.synchronizedList()获取线程安全的LinkedList
  • ③不支持随机访问和随机插入数据

注:ArrayDeque遍历采用iterator遍历。

ArrayDeque底层实现相关推荐

  1. ArrayDeque中的取余

    Java里有一个叫做Stack的类,却没有叫做Queue的类(它是个接口名字).而当需要使用栈时,Java已不推荐使用Stack,而是推荐使用更高效的ArrayDeque:既然Queue只是一个接口, ...

  2. Java—Queue队列详解(Deque/PriorityQueue/Deque/ArrayDeque/LinkedList)

    Queue Queue队列介绍   Queue是用于模拟队列的,啥叫队列?队列就是排队的意思,比如排队结账,先进入队伍中,先排到先付账走人:后排到的,进入队伍,等前面的人出队伍后,再跟在后面付钱出队. ...

  3. ArrayDeque简介说明

    转自: ArrayDeque简介说明 下文笔者讲述ArrayDeque简介说明 ArrayDeque简介 ArrayDeque是Deque接口的一种实现 依赖于可变数组来实现的 ArrayDeque没 ...

  4. java arraydeque poll,Java ArrayDeque

    Queue是什么 Queue是具有队列特性的接口 Queue具有先进先出的特点 Queue所有新元素都插入队列的末尾,移除元素都移除队列的头部 public interface Queue exten ...

  5. 常见Java集合的实现细节

    1. Set和Map Set代表一种集合元素无序.集合元素不可重复的集合,Map则代表一种由多个key-value对组成的集合,Map集合类似于传统的关联数组.表面上看它们之间相似性很少,但实际上Ma ...

  6. Java Review - Queue和Stack 源码解读

    文章目录 Pre 概述 Queue Deque ArrayDeque 一览 构造函数 属性 方法 addFirst() addLast() pollFirst() pollLast() peekFir ...

  7. 10.JAVA中的集合(数据结构)

    Java中的集合 包含以下结构: 数组-线性表 链表 栈 队列 散列表 二叉树 映射关系(key-value) List集合  特点:[有序.重复] [线性表--数组] ArrayList 定义 线程 ...

  8. java自学--容器

    容器分类 java容器主要分为三类:collection,map,iterator. 一.collection (一)list接口(元素有序且可以重复,通过索引来访问) 1.ArrayList 底层数 ...

  9. Electron + vue搭建项目

    声明 本人也在不断的学习和积累中,文章中有不足和误导的地方还请见谅,可以给我留言指正.希望和大家共同进步,共建和谐学习环境. 背景 最近公司想要开发一款桌面应用,在众多的跨平台桌面框架中,我选择了el ...

最新文章

  1. IP SOC与Camera ISP
  2. 什么是java多线程_什么是java多线程,java多线程的基本原理?
  3. Java OkHttp3的简单使用
  4. 导出目录结构_Selenium Webdriver 3.X源码分析之核心目录结构
  5. 轻量级KVO ——》 KVO 管理 observeValueForKeyPath
  6. 关于烂代码的那些事——什么是好代码
  7. Windows下用命令行导出导入MySQL数据库
  8. [渝粤教育] 西南科技大学 英语语法1 在线考试复习资料
  9. centos7安装oracle12c 三
  10. 一、express 路由 todos案例
  11. 插入空行_如何一键插入表格空行,这个方法才最高级!
  12. Docker容器的简单操作及应用部署
  13. Hadoop实战经验之HDFS故障排除-尚硅谷大数据培训
  14. hive sql 行列转换
  15. MAX262程控滤波器
  16. 金蝶KIS专业版V14.1下载链接,金蝶KIS专业版V14.1新增功能介绍 安装包下载地址
  17. 国外近年智慧出行项目清单
  18. 13 年的 Bug 调试经验总结(来自蜗牛学院)
  19. Java 小练习(图形面积计算器)
  20. win10系统如何设置局域网服务器,小编解决win10系统设置局域网的解决方法

热门文章

  1. 常用RPC框架及如何设计一个RPC框架
  2. 元胞自动机 | Matlab实现基于CA元胞自动机的生命游戏模拟
  3. 神经网络中的常用算法-BP算法
  4. 计算机病毒危害性分析,计算机病毒的毒性暨危害性分析系统
  5. 【JavaScript】动态显示当前时间
  6. consul服务注册与健康检查
  7. MergeVersion
  8. 网络上的计算机病毒怎么办,电脑中病毒怎么办  电脑病毒的危害有哪些
  9. 多线程与高并发整理总结【超全面】
  10. 创龙AD+全志T3 ad_display 开发案例