数组队列如何手撕?解密ArrayBlockingQueue的实现内幕!
队列
聊起队列,你一定会联想到一个与队列相似的数据结构:栈
。
为了更好的理解什么是队列,我们将它和栈来比较一下:
队列的特点是:先进先出
,如下图,1先进,1就先出。
栈的特点是:先进后出
,如下图,1先进,1却最后出。
为了让你更好的区分与理解队列与栈,你只记住这个口诀:吃多了拉就是队列,吃多了吐就是栈
。
哈哈哈,千万不要告诉别人这是我告诉你的....
数组实现队列,难在哪?
数组实现队列,乍一看,很贴合的一种方式去实现队列。仔细推敲一下你会发现并不简单。
为了更看清数组存在什么问题,我先new一个size为4的数组:
下一步,向数组中入列4个元素,数组按照0、1、2、3的下标被填充:
继续,消费两个元素,我们按照先进先出的原则,消费掉下标为0、1的元素,如图:
如果继续入列,你会发现:最大的下标3已经被占用,而数组依然存在空间。无法入列,造成空间浪费!
数组队列如何解决困难?
思维活跃的程序员,不可能因为遇到困难而畏惧!更不可能存在空间,而无法使用。就像饥肠辘辘,食物摆在眼前,却不知道如何吃掉它!
方案一:扩
了解过ArrayList
、HashMap
的朋友肯定知道空间不够,那就扩
。当发现空间不够用,我们是否可以将空间扩成原来的2倍?
,像下图这样:
是办法,但是不是最好的办法。因为下标为0和1的空间,无法利用和回收,造成浪费!
,不妥!也不可取!有人说无所谓,钱不是问题,金主直接return~
方案二:移
既然存在空间无法利用,那发现数组空间满了之后,是否可以将元素进行挪动
呢?如图:
是办法,是比方案一更好的办法,但依然不是最好的办法。那这又为什么呢?想想,两个数据的挪动,貌似没有什么成本,但是如果是1万甚至更多元素挪动,会带来什么呢?对!更高的时间复杂度,即o(n)。
除了扩
和移
,真的没有其他的办法?有的,今天给大家介绍一个新玩法:循环队列
。
循环数组队列
说起循环,大家肯定脑海中呈现出头尾相连的一个环,就是循环。是的!循环数组队列就是这样的结构,将你认知的连续的长长的数组,抽象成一个环
,就是下图:
在图5数组队列的基础上,继续入列一个元素,如下图:
入列后的数组,你试着把它想象成一个环状,如果再继续入列元素,会放到下标为1的位置。
因此循环数组队列很好地解决了之前两种方案的缺点:
1.空间浪费无法充分利用
2.数据迁移导致的o(n)的时间复杂度
如何实现循环数组队列
属性定义&说明
实现循环数组队列,需要定义几个关键的属性,以帮助我们更好地代码实现:
capicity
队列的容量大小items
定义的数组,容纳元素head
出列的头部数组下标,指向下次出列的位置tail
入列的尾部数组下标,指向下次入列的位置size
队列入列的元素个数
代码实现
public class LoopQueue<T> {
// 容量
private int capicity;
// 队列元素
private Object[] items;
// 头部位置
private int head = 0;
// 尾部位置
private int tail = 0;
// 大小
private int size = 0;
public LoopQueue(int capicity) {
this.capicity = capicity;
this.items = new Object[capicity];
}
public boolean enqueue(T t) {
// 如果超出容量,直接返回入列失败
if (size >= capicity) {
return false;
}
// 到尾部了,但是还没有满,tail重置
if (tail == capicity) {
tail = 0;
}
// 数组tail位置放置元素,并将tail自增
this.items[tail++] = t;
// 大小累加
size++;
return true;
}
public T dequeue() {
// 队列无元素,直接返回空
if (size <= 0) {
return null;
}
// 到尾部了,但是还没有满,head重置
if (head == capicity) {
head = 0;
}
// size自减少
size--;
// 获取元素并自增head
return (T) this.items[head++];
}
public static void main(String[] args) {
LoopQueue<Integer> queue = new LoopQueue<Integer>(6);
for (int i = 0; i < 7; i++) {
System.out.println(queue.enqueue(i));
}
for (int i = 0; i < 7; i++) {
System.out.println(queue.dequeue());
}
}
}
上述代码,已经增加了注释,你可以直接拷贝试验下。
main方法中可直接运行测试:定义容量为6的数组队列,入列7次后,出列7次
,运行结果如图:
位置1
因为空间已满,无法入列,返回false位置2
因为队列已空,出列的元素为空
ArrayBlockingQueue解密
为了研究下数组队列在JDK中如何实现的,我特地看了下ArrayBlockingQueue
的源码:
- put方法入口,当空间满了,
阻塞
:
- 如果空间没有满,进入第2步,
enqueue方法
,实现逻辑也采用的循环数组队列
:
写在最后
数组队列用了较长的篇幅在讨论,采用循环的思路很优雅地解决了空间利用问题。
不知道你有没有发现一个问题,JDK中的ArrayBlockingQueue
的构造函数全部都需要初始化capacity
:
因为使用数组需要初始化空间,所以需要初始化capacity。
留一道思考题:有没有实现无限空间的队列的方法呢?
不妨打开JDK寻找下答案~
我是面试怪圈的kazz,访问面试怪圈官网-www.msgqer.com下载无限免费资料!欢迎添加公众号一起讨论:
数组队列如何手撕?解密ArrayBlockingQueue的实现内幕!的更多相关文章
- java实现二叉树的Node节点定义手撕8种遍历(一遍过)
java实现二叉树的Node节点定义手撕8种遍历(一遍过) 用java的思想和程序从最基本的怎么将一个int型的数组变成Node树状结构说起,再到递归前序遍历,递归中序遍历,递归后序遍历,非递归前序遍 ...
- ThreadPoolExecutor的三种队列SynchronousQueue,LinkedBlockingQueue,ArrayBlockingQueue
SynchronousQueue SynchronousQueue是无界的,是一种无缓冲的等待队列,但是由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加:可以认为Sync ...
- NN入门,手把手教你用Numpy手撕NN(一)
前言 这是一篇包含极少数学推导的NN入门文章 大概从今年4月份起就想着学一学NN,但是无奈平时时间不多,而且空闲时间都拿去做比赛或是看动漫去了,所以一拖再拖,直到这8月份才正式开始NN的学习. 这篇文 ...
- NN入门,手把手教你用Numpy手撕NN(三)
NN入门,手把手教你用Numpy手撕NN(3) 这是一篇包含极少数学的CNN入门文章 上篇文章中简单介绍了NN的反向传播,并利用反向传播实现了一个简单的NN,在这篇文章中将介绍一下CNN. CNN C ...
- 编译原理--05 用C++手撕PL/0
前言 目录 01 文法和语言.词法分析复习 02 自顶向下.自底向上的LR分析复习 03 语法制导翻译和中间代码生成复习 04 符号表.运行时存储组织和代码优化复习 05 用C++手撕PL/0 在之前 ...
- C语言 简单的队列(数组队列)
//简单的队列 #include<stdio.h> #include<stdlib.h> #define datatype int #define N 10 //定义队列结构体 ...
- Java中的自定义数组队列
在Java中,作为所有数据结构中存储和获取速度最快的一种,数组凭借其这种简单易用的优势在各个方面都能大显神威.但是数组也有自身的局限性.数组的长度必须是固定的一旦定义之后就无法动态的更改,这就会造成这 ...
- Netty实现高性能IOT服务器(Groza)之手撕MQTT协议篇上
前言 诞生及优势 MQTT由Andy Stanford-Clark(IBM)和Arlen Nipper(Eurotech,现为Cirrus Link)于1999年开发,用于监测穿越沙漠的石油管道.目标 ...
- 手撕RPC框架
手撕RPC 使用Netty+Zookeeper+Spring实现简易的RPC框架.阅读本文需要有一些Netty使用基础. 服务信息在网络传输,需要讲服务类进行序列化,服务端使用Spring作为容器.服 ...
随机推荐
- [luogu5361]热闹的聚会与尴尬的聚会
由于两者是独立的,我们希望两者的$p$和$q$都最大 考虑最大的$p$,先全部邀请,此时要增大$p$显然必须要删去当前度数最小的点,不断删除之后将每一次度数最小值对答案取max即可 对于$q$也即最大 ...
- [bzoj1082]栅栏
先二分答案,然后搜索暴力判断由于数据范围较大,需要剪枝:1.当前所有可能被用到的木板长度和(长度要不小于最小所需长度)>=所要拼成的所有木板的和:2.对于需求从大到小枚举木板(这样一开始枚举次数 ...
- tomcat更改端口号and设置cmd别名
1.修改端口号 打开tomcat的conf\server.xml 这个是项目访问的端口号了 这个也要注意更改一下,避免冲突 2.接下来要设置cmd别名 用文本编辑器打开bin\catalina.bat ...
- 【Cloud Computing】Hadoop环境安装、基本命令及MapReduce字数统计程序
[Cloud Computing]Hadoop环境安装.基本命令及MapReduce字数统计程序 1.虚拟机准备 1.1 模板机器配置 1.1.1 主机配置 IP地址:在学校校园网Wifi下连接下 V ...
- Linux远程软件
Xhell6:Linux的终端模拟软件 1>安装并破解:解压.破解(运行两个.bat文件).启动(点击Xshell.exe文件) 2>连接远端Linux系统: 创建会话:点击连接,在常规框 ...
- 『MdOI R1』Treequery
我们可以思考怎么做呢. 首先我们需要进行一些分类讨论: 我们先思考一下如果所有关键点都在 \(p\) 的子树内, 那显然是所有关键点的 \(Lca\) 到 \(p\) 距离. 如果所有关键点一些在 \ ...
- Codeforces 356E - Xenia and String Problem(哈希)
Codeforces 题面传送门 & 洛谷题面传送门 首先显然一个 gray 串的长度只可能是 \(2^k-1\),其中 \(k\in\mathbb{Z}\). 考虑将一个字符改成另外一个字符 ...
- Codeforces 690A2 - Collective Mindsets (medium)
Codeforces 题面传送门 & 洛谷题面传送门 一道脑筋急转弯的结论题. 首先我们考虑对于某个特定的金币数 \(m\),有哪些 \(n\) 满足条件.考虑最 naive 的情况,\(m= ...
- Kubernetes(K8s)部署 SpringCloud 服务实战
1. 概述 老话说的好:有可能性就不要放弃,要敢于尝试. 言归正传,之前我们聊了一下如何在 Kubernetes(K8s)中部署容器,今天我们来聊一下如何将 SpringCloud 的服务部署到 Ku ...
- 【豆科基因组】豇豆Cowpea,Vigna unguiculata [L.] Walp.基因组2019PJ
目录 来源 结果 基因组大小估计 采用stitching方法组装 修改豇豆染色体编号 基因注释和重复DNA 豇豆遗传多样性 SNP和INDEL Vu03 上 4.2-Mb 染色体倒位的鉴定 与其他暖季 ...