JDK容器学习之Queue: ArrayDeque
时间:2022-04-27
本文章向大家介绍JDK容器学习之Queue: ArrayDeque,主要内容包括数组双端队列 ArrayDeque、2. 常见接口实现方式、3. 使用姿势&小结、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。
数组双端队列 ArrayDeque
双端队列,表示可以添加元素到(或删除,获取)队列头也可以添加元素到(或删除,获取)队列尾
1. 底层数据结构
类中定义成员变量,一个数组和两个int
transient Object[] elements;
transient int head;
transient int tail;
数据结构比较清晰,就是一个数组,head指向队列的头,tail指向队列的尾
数组定义要求数组的容量为2的n次幂
2. 常见接口实现方式
删除元素
先看删除逻辑,因为比较简单,实现如下
public E poll() {
if (size == 0) // 队列为空
return null;
int s = --size;
modCount++;
// 数组中第一个为队列头
E result = (E) queue[0];
E x = (E) queue[s];
queue[s] = null;
if (s != 0)
// 队列非空时,重排剩下的元素
siftDown(0, x);
return result;
}
添加元素
在队头和队尾添加的逻辑基本一致,这里以在队列尾添加元素绩进行分析
public void addLast(E e) {
if (e == null) // 不支持向队列中塞入null对象
throw new NullPointerException();
elements[tail] = e;
if ( (tail = (tail + 1) & (elements.length - 1)) == head) {
// tail 后移一位,若tail长度超过数组长度,则tail转到数组头
// 上面的与操作等价于对数组长度进行求余
// 若tail和head相等,则表示数组内容填充满,需要扩容
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 p
// 新的容量为原来的两倍
int newCapacity = n << 1;
if (newCapacity < 0) // int逸出判断
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;
}
下面是一个数组扩容的逻辑示意图
- 图中上面的为原数组,添加元素8之后,tail和head相等,都是5
- 原队列容量为8,顺序为
1 -> 9 -> 5 -> 7 -> 4 -> 3 -> 5 -> 8
- 扩容后,数组容量为16,顺序保持不变,head为0,tail为8
弹出元素
以弹出队头的元素为例,会直接返回队列头的元素,并将head前移一位,并将数组中源队列头的数据清掉(赋值为null)
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;
}
删除元素的示意图如下
3. 使用姿势&小结
- 若能确定队列的容量,在使用时请指定初始化容量,避免频繁的扩容
- 数组的实际容量必须为2的n次幂;初始化时传入一个非2的n次幂的容量参数时,会自动查找到刚好大于该参数的2的n次幂作为数组的实际长度
- 队列中不能有空元素
- 只有向队列中添加元素超过容量时,才会触发扩容逻辑(扩容为之前的两倍)
- 扩容后,数组中的实际顺序和队列顺序一致(即head会指向0,设计到数组的重排)
- head指向的是队列中第一个元素的下标位置;tail指向的是队列最后一个元素的后一位索引
- 队列头添加元素,是在head前一个数组位置处赋值;在队列尾添加元素是直接在tail指向的数组位置赋值
- 队列未发生扩容时,出队和进队都不会导致数组重排,只会改变head或tail的值而已
- Golang context 包入门
- 动手实现一个JSON验证器(上)
- Go语言实现冒泡和快速排序
- 利用Meta申明来做百度、谷歌、雅虎、微软等搜索的开放适配
- 【Oracle 12c ASM专题】——我的第一个Flex Diskgroup
- 编写一个go gRPC的服务
- GO语言使用gopsutil包进行机器信息采集
- redigo 连接池代码分析
- golang文件传输服务
- golang使用信号量热更新
- SEO养博客神器:同步文章(或摘要)到新浪博客的WordPress插件
- go如何读取MAC地址或硬盘ID
- SEO分享:让百度删除不想收录的域名或快照的最快方法
- MySQL中server_id一致带来的问题
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 树莓派基础实验20:火焰报警传感器实验
- (译)SDL编程入门(8)几何图形渲染
- Java8 dubbo 调用 Collectors.toMap代码片发生的异常(IllegalStateException: Duplicate key)
- 树莓派基础实验21:烟雾报警传感器实验
- 树莓派基础实验22:红外遥控传感器实验
- Spring的BeanUtil的copyProperties方法 慎用!!
- (译)SDL编程入门(9)视口
- (译)SDL编程入门(7)纹理加载和渲染
- 三步带你开发一个短链接生成平台
- 绕安全狗的那些事
- single-spa 基础概念
- DVWA-对Command Injection(命令注入)的简单演示与分析
- 如何探测内网存活主机
- Java ServletContext详解
- Java web Cookie详解(持久化+原理详解+共享问题+设置中文+发送多个Cookie)