一、概述

  队列,又称为伫列(queue),是先进先出(FIFO, First-In-First-Out)的线性表。在具体应用中通常用链表或者数组来实现。队列只允许在后端(称为rear)进行插入操作,在前端(称为front)进行删除操作。队列的操作方式和堆栈类似,唯一的区别在于队列只允许新数据在后端进行添加。

  在Java中队列又可以分为两个大类,一种是阻塞队列和非阻塞队列。

  1、没有实现阻塞接口:

  1)实现java.util.Queue的LinkList,

  2)实现java.util.AbstractQueue接口内置的不阻塞队列: PriorityQueue 和 ConcurrentLinkedQueue

  2、实现阻塞接口的

  java.util.concurrent 中加入了 BlockingQueue 接口和五个阻塞队列类。它实质上就是一种带有一点扭曲的 FIFO 数据结构。不是立即从队列中添加或者删除元素,线程执行操作阻塞,直到有空间或者元素可用。
  五个队列所提供的各有不同:
  * ArrayBlockingQueue :一个由数组支持的有界队列。
  * LinkedBlockingQueue :一个由链接节点支持的可选有界队列。
  * PriorityBlockingQueue :一个由优先级堆支持的无界优先级队列。
  * DelayQueue :一个由优先级堆支持的、基于时间的调度队列。
  * SynchronousQueue :一个利用 BlockingQueue 接口的简单聚集(rendezvous)机制。
  队列是Java中常用的数据结构,比如在线程池中就是用到了队列,比如消息队列等。

  由队列先入先出的特性,我们知道队列数据的存储结构可以两种,一种是基于数组实现的,另一种则是基于单链实现。前者在创建的时候就已经确定了数组的长度,所以队列的长度是固定的,但是可以循环使用数组,所以这种队列也可以称之为循环队列。后者实现的队列内部通过指针指向形成一个队列,这种队列是单向且长度不固定,所以也称之为非循环队列。下面我将使用两种方式分别实现队列。

  二、基于数组实现循环队列

  由于在往队列中放数据或拉取数据的时候需要移动数组对应的下标,所以需要记录一下队尾和队头的位置。说一下几个核心的属性吧:

  1、queue:队列,object类型的数组,用于存储数据,长度固定,当存储的数据数量大于数组当度则抛出异常;

  2、head:队头指针,int类型,用于记录队列头部的位置信息。

  3、tail:队尾指针,int类型,用于记录队列尾部的位置信息。

  4、size:队列长度,队列长度大于等于0或者小于等于数组长度。

 /**
* 队列管道,当管道中存放的数据大于队列的长度时将不会再offer数据,直至从队列中poll数据后
*/
private Object[] queue;
//队列的头部,获取数据时总是从头部获取
private int head;
//队列尾部,push数据时总是从尾部添加
private int tail;
//队列长度
private int size;
//数组中能存放数据的最大容量
private final static int MAX_CAPACITY = <<;
//数组长度
private int capacity;
//最大下标
private int maxIndex;

  

  三、数据结构

  图中,红色部分即为队列的长度,数组的长度为16。因为这个队列是循环队列,所以队列的头部不一定要在队列尾部前面,只要队列的长度不大于数组的长度就可以了。

  四、构造方法

  1、MyQueue(int initialCapacity):创建一个最大长度为 initialCapacity的队列。

  2、MyQueue():创建一个默认最大长度的队列,默认长度为16;

    public MyQueue(int initialCapacity){
if (initialCapacity > MAX_CAPACITY)
throw new OutOfMemoryError("initialCapacity too large");
if (initialCapacity <= )
throw new IndexOutOfBoundsException("initialCapacity must be more than zero");
queue = new Object[initialCapacity];
capacity = initialCapacity;
maxIndex = initialCapacity - ;
head = tail = -;
size = ;
}
public MyQueue(){
queue = new Object[];
capacity = ;
head = tail = -;
size = ;
maxIndex = ;
}

  五、往队列添加数据

  添加数据时,首先判断队列的长度是否超出了数组的长度,如果超出了就添加失败(也可以设置成等待,等到有人消费了队列里的数据,然后再添加进去)。都是从队列的尾部添加数据的,添加完数据后tail指针也会相应自增1。具体实现如一下代码:

/**
* 往队列尾部添加数据
* @param object 数据
*/
public void offer(Object object){
if (size >= capacity){
System.out.println("queue's size more than or equal to array's capacity");
return;
}
if (++tail > maxIndex){
tail = ;
}
queue[tail] = object;
size++;
}

  六、向队列中拉取数据

  拉取数据是从队列头部拉取的,拉取完之后将该元素删除,队列的长度减一,head自增1。代码如下:

    /**
* 从队列头部拉出数据
* @return 返回队列的第一个数据
*/
public Object poll(){
if (size <= ){
System.out.println("the queue is null");
return null;
}
if (++head > maxIndex){
head = ;
}
size--;
Object old = queue[head];
queue[head] = null;
return old;
}

  

  七、其他方法

  1、查看数据:这个方法跟拉取数据一样都是获取到队列头部的数据,区别是该方法不会将对头数据删除:

/**
* 查看第一个数据
* @return
*/
public Object peek(){
return queue[head];
}

  2、清空队列:

/**
* 清空队列
*/
public void clear(){
for (int i = ; i < queue.length; i++) {
queue[i] = null;
}
tail = head = -;
size = ;
}

  完整的代码如下:

/**
* 队列
*/
public class MyQueue { /**
* 队列管道,当管道中存放的数据大于队列的长度时将不会再offer数据,直至从队列中poll数据后
*/
private Object[] queue;
//队列的头部,获取数据时总是从头部获取
private int head;
//队列尾部,push数据时总是从尾部添加
private int tail;
//队列长度
private int size;
//数组中能存放数据的最大容量
private final static int MAX_CAPACITY = <<;
//数组长度
private int capacity;
//最大下标
private int maxIndex; public MyQueue(int initialCapacity){
if (initialCapacity > MAX_CAPACITY)
throw new OutOfMemoryError("initialCapacity too large");
if (initialCapacity <= )
throw new IndexOutOfBoundsException("initialCapacity must be more than zero");
queue = new Object[initialCapacity];
capacity = initialCapacity;
maxIndex = initialCapacity - ;
head = tail = -;
size = ;
}
public MyQueue(){
queue = new Object[];
capacity = ;
head = tail = -;
size = ;
maxIndex = ;
} /**
* 往队列尾部添加数据
* @param object 数据
*/
public void offer(Object object){
if (size >= capacity){
System.out.println("queue's size more than or equal to array's capacity");
return;
}
if (++tail > maxIndex){
tail = ;
}
queue[tail] = object;
size++;
} /**
* 从队列头部拉出数据
* @return 返回队列的第一个数据
*/
public Object poll(){
if (size <= ){
System.out.println("the queue is null");
return null;
}
if (++head > maxIndex){
head = ;
}
size--;
Object old = queue[head];
queue[head] = null;
return old;
} /**
* 查看第一个数据
* @return
*/
public Object peek(){
return queue[head];
} /**
* 队列中存储的数据量
* @return
*/
public int size(){
return size;
} /**
* 队列是否为空
* @return
*/
public boolean isEmpty(){
return size == ;
} /**
* 清空队列
*/
public void clear(){
for (int i = ; i < queue.length; i++) {
queue[i] = null;
}
tail = head = -;
size = ;
} @Override
public String toString() {
if (size <= ) return "{}";
StringBuilder builder = new StringBuilder(size + );
builder.append("{");
int h = head;
int count = ;
while (count < size){
if (++h > maxIndex) h = ;
builder.append(queue[h]);
builder.append(", ");
count++;
}
return builder.substring(,builder.length()-) + "}";
}
}

  

  八、总结:

  1、该队列为非线程安全的,在多线程环境中可能会发生数据丢失等问题。

  2、队列通过移动指针来确定数组下标的位置,因为是基于数组实现的,所以队列的长度不能够超过数组的长度。

  3、该队列是循环队列,这就意味着数组可以重复被使用,避免了重复创建对象带来的性能的开销。

  4、添加数据时总是从队列尾部添加,拉取数据时总是从队列头部拉取,拉取完将对象元素删除。

  欢迎大家关注公众号: 【java解忧杂货铺】,里面会不定时发布一些技术博客;关注即可免费领取大量最新,最流行的技术教学视频:

教你如何使用Java手写一个基于数组实现的队列的更多相关文章

  1. 教你如何使用Java手写一个基于链表的队列

    在上一篇博客[教你如何使用Java手写一个基于数组的队列]中已经介绍了队列,以及Java语言中对队列的实现,对队列不是很了解的可以我上一篇文章.那么,现在就直接进入主题吧. 这篇博客主要讲解的是如何使 ...

  2. java手写的动态数组JimisunArray

    /** * @Author:jimisun * @Description: * @Date:Created in 22:10 2018-07-18 * @Modified By: */ public ...

  3. 手把手教你手写一个最简单的 Spring Boot Starter

    欢迎关注微信公众号:「Java之言」技术文章持续更新,请持续关注...... 第一时间学习最新技术文章 领取最新技术学习资料视频 最新互联网资讯和面试经验 何为 Starter ? 想必大家都使用过 ...

  4. 【spring】-- 手写一个最简单的IOC框架

    1.什么是springIOC IOC就是把每一个bean(实体类)与bean(实体了)之间的关系交给第三方容器进行管理. 如果我们手写一个最最简单的IOC,最终效果是怎样呢? xml配置: <b ...

  5. 搞定redis面试--Redis的过期策略?手写一个LRU?

    1 面试题 Redis的过期策略都有哪些?内存淘汰机制都有哪些?手写一下LRU代码实现? 2 考点分析 1)我往redis里写的数据怎么没了? 我们生产环境的redis怎么经常会丢掉一些数据?写进去了 ...

  6. 利用SpringBoot+Logback手写一个简单的链路追踪

    目录 一.实现原理 二.代码实战 三.测试 最近线上排查问题时候,发现请求太多导致日志错综复杂,没办法把用户在一次或多次请求的日志关联在一起,所以就利用SpringBoot+Logback手写了一个简 ...

  7. java 手写 jvm高性能缓存

    java 手写 jvm高性能缓存,键值对存储,队列存储,存储超时设置 缓存接口 package com.ws.commons.cache; import java.util.function.Func ...

  8. 看年薪50W的架构师如何手写一个SpringMVC框架

    前言 做 Java Web 开发的你,一定听说过SpringMVC的大名,作为现在运用最广泛的Java框架,它到目前为止依然保持着强大的活力和广泛的用户群. 本文介绍如何用eclipse一步一步搭建S ...

  9. 摊牌了!我要手写一个“Spring Boot”

    目前的话,已经把 Spring MVC 相关常用的注解比如@GetMapping .@PostMapping .@PathVariable 写完了.我也已经将项目开源出来了,地址:https://gi ...

随机推荐

  1. DDD实战进阶第一波(九):开发一般业务的大健康行业直销系统(实现经销商上下文仓储与领域逻辑)

    上篇文章主要讲述了经销商上下文的需求与POCO对象,这篇文章主要讲述该界限上下文的仓储与领域逻辑的实现. 关于界限上下文与EF Core数据访问上下文参考产品上下文相应的实现,这里不再累述. 因为在经 ...

  2. 移动 Web 的用户界面设计

    http://www.ibm.com/developerworks/cn/mobile/wa-interface/index.html 简介 在创新者试图探索新的可能性的同时,新兴技术也在经历快速变化 ...

  3. 解决Select标签的Option在IE浏览中display:none不生效的问题

    页面的Select标签,需要控制Select的Option不需要显示,根据条件来隐藏某些Option选项. 正常情况下使用hide()就能实现,hide()方法实际是给Option加上display属 ...

  4. 如何在python脚本开发做code review

    在软件项目开发中,我们经常提到一个词“code review”.code review中文翻译过来就是代码评审或复查,简而言之就是编码完成后由其他人通过阅读代码来检查代码的质量(可编译.可运行.可读. ...

  5. YOLO_Online 将深度学习最火的目标检测做成在线服务实战经验分享

    YOLO_Online 将深度学习最火的目标检测做成在线服务 第一次接触 YOLO 这个目标检测项目的时候,我就在想,怎么样能够封装一下让普通人也能够体验深度学习最火的目标检测项目,不需要关注技术细节 ...

  6. 学习了解CyclicBarrier

    CyclicBarrier我的理解就是一个线程等待器,用途就是将注册了这个barrier的线程卡在同一个位置,直到注册这个barrier的所有线程都完成之后,继续执行.下面是一个学习过程中采用的示例, ...

  7. C语言出来多久了你知道吗?

    在20世纪80年代,为了避免不同开发者使用的C语言语法的差异,美国国家标准局为C语言开发了一套完整的美国国家标准语言文法,称为ANSI C,作为C语言的初始标准.. [1] 2011年12月8日,国际 ...

  8. Centos7下安装MySql

    1.安装MariaDB 安装命令 yum -y install mariadb mariadb-server 安装完成MariaDB,首先启动MariaDB systemctl start maria ...

  9. python爬虫入门(三)XPATH和BeautifulSoup4

    XML和XPATH 用正则处理HTML文档很麻烦,我们可以先将 HTML文件 转换成 XML文档,然后用 XPath 查找 HTML 节点或元素. XML 指可扩展标记语言(EXtensible Ma ...

  10. python之字典、列表、元组生成器的使用

    python的生成式在一些类型相互转换的时候可以写出十分优雅的代码.如列表转换成另一个列表.字典.或元组.并且代码的执行效率也比使用for...in...循环高. 列表生成式 列表生成式即生成列表的生 ...