java 数据队列_Java 数据结构 - 队列

Java 数据结构 - 队列

我们今天要讲的数据结构是队列,比如 Java 线程池任务就是队列实现的。

1. 什么是队列

和栈一样,队列也是一种操作受限的线性结构。使用队列时,在一端插入元素,而在另一端删除元素。

5c0a34b9c704f00458b5d88dd36a055d.png

1.1 队列的主要特性

队列中的数据元素遵守 "先进先出"(First In First Out)的原则,简称 FIFO 结构。

限定只能在队列一端插入,而在另一端进行删除操作。

1.2 队列的相关概念

入队(enqueue):队列的插入操作。

出队(dequeue):队列的删除操作。

2. 复杂度分析

和栈一样,队列也有两种实现方案,我们简单分析一下这两种队列的复杂度:

动态队列(链表):也叫链式队列。其插入、删除时间复杂度都是 O(1)。

静态队列(数组):也叫顺序队列。当队列队尾指针移到最后时,此时有两种操作:一是进行简单的数据搬移,二是进行队列循环。

关于队列的实现,我们只实现如下的基本操作。

public interface Queue {

Queue enqueue(Object obj); // 入队

Object dequeue(); // 出队

int size(); // 元素个数

}

2.1 链式队列

链式队列的实现非常简单,其插入和删除的时间复杂度都是 O(1)。为了简化代码的实现,我们引入哨兵结点。如下图所示,head 头结点是哨兵结点,不保存任何数据,从 head.next 开始保存数据,tail 结点指向最后一个元素结点。链表队列头结点和尾结点说明:

头结点:head 结点为哨兵结点,不保存任何数据,数据从第二个结点开始。

尾结点:tail 结点指向最后一个数据结点。

d65cee1a70c3b8db30a007138d9f35cb.png

根据上图,我们可以轻松实现一个链表组成的队列,代码也很简单。

public class LinkedQueue implements Queue {

private Node head;

private Node tail;

private int size;

public LinkedQueue() {

head = new Node(null, null);

tail = head;

}

@Override

public Queue enqueue(Object obj) {

tail.next = new Node(obj, null);

tail = tail.next;

size++;

return this;

}

@Override

public Object dequeue() {

Node next = head.next;

if (next == null) {

return null;

}

head = head.next;

size--;

return next.item;

}

@Override

public int size() {

return size;

}

public static class Node {

private Object item;

private Node next;

public Node(Object item, Node next) {

this.item = item;

this.next = next;

}

}

}

2.2 顺序队列

如果是数组实现的队列,则比链表要复杂一些,当尾结点指向数组的最后一个位置时,没有剩余的空间存放数据时,此时该如何处理?通过我们有两种解决方案:

数据搬移:将 head ~ tail 结点的数据搬移到从 0 结点开始。

循环队列:tail 结点从 0 开始循环使用,不用搬移数据。

我们先看一下循环队列

9df1932c484c12ec473dee5c81af11f4.png

如上图所示,当尾结点指向数组最后一个位置,当 tail 指向数组最后位置时,触发数据搬移,将 head ~ tail 结点的数据搬移到从 0 结点开始。数组队列头结点和尾结点说明:

头结点:head 结点指向第一个数据结点。当 head == tail 时说明队列处于队空状态,直接返回 null。

尾结点:tail 结点指向最后一个数据之后的空结点。当 tail == capcity 时说明队列处于队满状态,需要扩容或进行数据搬移。

根据上述理论代码实现如下:

public class ArrayQueue implements Queue {

private Object[] array;

private int capcity;

// head指向第一个数据结点的位置,tail指向最后一个数据结点之后的位置

private int head;

private int tail;

public ArrayQueue() {

this.capcity = 1024;

this.array = new Object[this.capcity];

}

public ArrayQueue(int capcity) {

this.capcity = capcity;

this.array = new Object[capcity];

}

/**

* tail 指向数组最后位置时,需要触发扩容或数组搬移

* 1. head!=0 说明数组还有剩余的空间,将 head 搬运到队列 array[0]

* 2. head==0 说明数组没有剩余的空间,扩容

*/

@Override

public Queue enqueue(Object obj) {

if (tail == capcity) {

if (head == 0) {

resize();

} else {

rewind();

}

}

array[tail++] = obj;

return this;

}

@Override

public Object dequeue() {

if (head == tail) {

return null;

}

Object obj = array[head];

array[head] = null;

head++;

return obj;

}

// 将 head 搬运到队列 array[0]

private void rewind() {

for (int i = head; i < tail; i++) {

array[i - head] = array[i];

array[i] = null;

}

tail -= head;

head = 0;

}

// 扩容

private void resize() {

int oldCapcity = this.capcity;

int newCapcity = this.capcity * 2;

Object[] newArray = new Object[newCapcity];

for (int i = 0; i < oldCapcity; i++) {

newArray[i] = array[i];

}

this.capcity = newCapcity;

this.array = newArray;

}

@Override

public int size() {

return tail - head;

}

}

说明: 数组队列出队的时间复杂度始终是 O(1)。但入队时要分为三种情况:

有空间:大多数情况,也是最好时间复杂度 O(1)。

没有空间需要数据搬移:执行 n 后触发一次数据搬移,最坏时间复杂度 O(n)。

没有空间需要扩容:执行 n 后触发一次数据搬移,最坏时间复杂度 O(n)。

如果采用摊还分析法,最好时间复杂度 O(1),最坏时间复杂度 O(n),摊还时间复杂度为 O(1)。虽然,平均时间复杂度还是 O(1),但我们能不能不进行数据搬移,直接循环使用数组呢?

2.3 循环队列

循环队列是一种非常高效的队列,我们需要重点掌握它,要能轻松写出无 BUG 的循环队列。

b4b4a2c1ea8705e012d3435555935b2f.png

数组队列头结点和尾结点说明:

头结点:head 结点指向第一个数据结点。当 head == tail 时说明队列处于队空状态,直接返回 null。否则在元素出队后,需要重新计算 head 值。

尾结点:tail 结点指向最后一个数据之后的空结点。每次插入元素后重新计算 tail 值,当 tail == head 时说明队列处于队满状态,需要扩容。

元素位置:对数组长度取模 (tail + 1) % length ,所以这种数据为了提高效率,都要求数组长度为 2^n,通过位运算取模 (tail + 1) & (length - 1)。

public class ArrayCircularQueue implements Queue {

private Object[] array;

private int capcity;

// 头结点指向

private int head;

private int tail;

public ArrayCircularQueue() {

this.capcity = 1024;

this.array = new Object[this.capcity];

}

public ArrayCircularQueue(int capcity) {

this.capcity = capcity;

this.array = new Object[capcity];

}

@Override

public Queue enqueue(Object obj) {

array[tail] = obj;

if ((tail = (tail + 1) % capcity) == head) {

resize();

}

return this;

}

@Override

public Object dequeue() {

if (head == tail) {

return null;

}

Object obj = array[head];

array[head] = null;

head = (head + 1) % capcity;

return obj;

}

// 不扩容,要先判断能否往数组中添加元素

public Queue enqueue2(Object obj) {

if ((tail + 1) % capcity == head) return this;

array[tail] = obj;

tail = (tail + 1) % capcity;

return this;

}

// 扩容

private void resize() {

// 说明还有空间

if (head != tail) {

return;

}

int oldCapcity = this.capcity;

int newCapcity = this.capcity * 2;

Object[] newArray = new Object[newCapcity];

for (int i = head; i < oldCapcity; i++) {

newArray[i - head] = array[i];

}

for (int i = 0; i < head; i++) {

newArray[capcity - head + i] = array[i];

}

this.capcity = newCapcity;

this.array = newArray;

this.head = 0;

this.tail = oldCapcity;

}

@Override

public int size() {

return tail - head;

}

}

说明: 循环队列关键是判断队空和空满的状态分别进行处理。除开扩容操作,循环队列的入队和出队的时间复杂度都是 O(1),同时也可以充分利用 CPU 缓存,所以说一种高效的数据结构。

2.4 阻塞队列和并发队列

3. 队列在软件工程中应用

如 JDK 线程池,当线程池没有空闲线程时,新的任务请求线程资源时,线程池该如何处理?各种处理策略又是如何实现的呢?我们一般有两种处理策略。

非阻塞的处理方式,直接拒绝任务请求;

阻塞的处理方式,将请求排队,等到有空闲线程时,取出排队的请求继续处理。

每天用心记录一点点。内容也许不重要,但习惯很重要!

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

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

相关文章

java中死锁_关于java中死锁的总结

关于死锁&#xff0c;估计很多程序员都碰到过&#xff0c;并且有时候这种情况出现之后的问题也不是非常好排查&#xff0c;下面整理的就是自己对死锁的认识&#xff0c;以及通过一个简单的例子来来接死锁的发生&#xff0c;自己是做python开发的&#xff0c;但是对于死锁的理解…

eclipse java的jvm匹配_eclipse设置jvm

Java虚拟机默认分配64M内存&#xff0c;如果你的应用比较大&#xff0c;超出64M内存&#xff0c;Java虚拟机就会抛出outOfMemoryError&#xff0c;并停止运行。不管是什么应用(Web应用、Application等)&#xff0c;只需要修改你的机器上的运行Java命令&#xff0c;在javaxxx命令…

java+set+split_阿里资深工程师教你如何优化 Java 代码!

原标题&#xff1a;阿里资深工程师教你如何优化 Java 代码&#xff01;作者 | 王超责编 | 伍杏玲明代王阳明先生在《传习录》谈为学之道时说&#xff1a;私欲日生&#xff0c;如地上尘&#xff0c;一日不扫&#xff0c;便又有一层。着实用功&#xff0c;便见道无终穷&#xff0…

java返回特定下标元素_java基础--输出数组中指定元素的下标

java基础--输出数组中指定元素的下标java基础--输出数组中指定元素的下标package com.lcn.day05;public class ArrayDemo8 {/*** 输出数组指定元素的下标*/public static void main(String[] args) {//定义一个数组int[] array new int[]{123,456,789,321,654,987};int index …

java钱币换算_Java编写钱币转换为大写程序

import java.text.DecimalFormat;public class ChangeMonney {private static final String[] STR_UNIT {"零", "壹", "贰", "叁", "肆", "伍","陆", "柒", "捌", "玖"…

java mongodb 使用场景_mongodb使用场景一般是什么?mongodb有哪些基本命令?

此前我们已经充分了解过了mongodb的原理及各种概念&#xff0c;你们知道一般在什么场景下才会使用mongodb吗?下面通过文章来了解一下吧。一、mongodb基本命令1)、数据库切换use admin;2)、浏览当前数据库db;3)、浏览全部数据库show dbs;4)、删除数据库use test;db.dropDatabas…

java desktop mailto,mailto在Java?

I am trying to set a hyperlink in my eclipse java project.when someone clicks on a button, it should open up an email client along with the givenemail id. is it possible to implement it with java.awt.Desktop?解决方案Yes it is possible using desktop.mail()…

myVariable是java标识符吗_java 标识符与变量

一、Java 标识符三要素1.标识符由字母、下划线(_)、美元符号($)或者字母组成。2.标识符应以字母、下划线(_)、美元符开头。3.标识符字符大小写敏感&#xff0c;长度无限制。标识符最重要的就是 见名知意并且不能与java关键字重名!二、Java 变量1.java变量是程序中最基本的单元。…

使java代码更干净_java如何使用Lombok更优雅地编码

Lombok简介和其他语言相比&#xff0c;Java经常因为不必要的冗长被批评。Lombok提供了一系列注解用以在后台生成模板代码&#xff0c;将其从你的类中删除&#xff0c;从而有助于保持你的代码整洁。较少的模板意味着更简洁的代码&#xff0c;更易于阅读和维护。在本文中&#xf…

java自动创建月份_使用Java根据月份动态绘制BarGraph

您需要通过每次更改来更新数据集.我添加了一个updateDataset()方法,并在几个关键位置调用了它.private void updateDataset() {dataset.clear();for (int i 1; i < finalday; i) {dataset.setValue(i, "Marks", "" i);}笔记&#xff1a;>不要使用绝…

mysql 5.6.23 源码包安装报错_Ubuntu 14.10下编译安装MySQL 5.6.23

1. 安装环境&#xff1a;Ubuntu Server 14.10MySQL-5.6.23.tar.gz2. 安装必备的工具sudo apt-get install make bison g build-essential libncurses5-dev cmake3. 添加组合用户 设置安装目录权限sudo groupadd mysqlsudo useradd –g mysql mysql –s /bin/false #创建用户mys…

java 阻塞 性能_聊聊并发-Java中的阻塞队列

1. 什么是阻塞队列&#xff1f;阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是&#xff1a;在队列为空时&#xff0c;获取元素的线程会等待队列变为非空。当队列满时&#xff0c;存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景…

java编译器对代码的优化_使用最终局部变量的Java编译器优化

我一直认为final关键字对局部方法变量或参数没有影响。因此,我尝试测试以下代码,但似乎我错了:private static String doStuffFinal() {final String a "A";final String b "B";final int n 2;return a b n;}private static String doStuffNotFinal()…

友盟统计java代码_SFAnalytics 分析友盟统计源码,反编译 SDK,还有部分没有 出来 android 259万源代码下载- www.pudn.com...

文件名称: SFAnalytics下载 收藏√ [5 4 3 2 1 ]开发工具: Java文件大小: 8023 KB上传时间: 2016-06-05下载次数: 0提 供 者: 花心大萝卜详细说明&#xff1a;分析友盟统计源码&#xff0c;反编译友盟统计SDK&#xff0c;还有部分没有反编译出来-Analysis their Allies s…

Java导入sql失败_java – 在hibernate中导入import.sql失败

我希望每次应用程序运行时自动删除表并创建一个新表,并自动插入预定义数据.我已经在import.sql中准备数据了.我已经在application.properties中设置了spring.jpa.hibernate.ddl-auto create-drop.但是,为什么我会收到以下错误&#xff1f;我可以手动插入它.2015-11-20 20:53:5…

java mvc数据库 封装_关于SpringMvc参数封装_JavaEE框架(Maven+SpringMvc+Spring+MyBatis)全程实战教程_Java视频-51CTO学院...

SpringMVCSpring MVC属于SpringFrameWork的后续产品&#xff0c;已经融合在Spring Web Flow里面。Spring MVC 分离了控制器、模型对象、分派器以及处理程序对象的角色&#xff0c;这种分离让它们更容易进行定制。SpringSpring是一个开源框架&#xff0c;Spring是于2003 年兴起的…

Java语言有哪几种访问权限_java基础之java四种访问权限详解

引言Java中的访问权限理解起来不难&#xff0c;但完全掌握却不容易&#xff0c;特别是4种访问权限并不是任何时候都可以使用。下面整理一下&#xff0c;在什么情况下&#xff0c;有哪些访问权限可以允许选择。一、访问权限简介访问权限控制&#xff1a; 指的是本类及本类内部的…

centos 使用java_如何在CentOS 8上安装Java

Java是用于构建不同类型的应用程序和系统的最流行的编程语言之一。Java有两种不同的实现&#xff0c;OpenJDK和Oracle Java&#xff0c;两者之间几乎没有区别&#xff0c;只是Oracle Java具有一些其他的商业功能。 Oracle Java License仅允许该软件的非商业使用&#xff0c;例如…

kafka java客户端加密_kafka消息加密(SASL/PLAIN)

kafka消息加密(SASL/PLAIN)具体的配置方式官网已经说的很清楚了(尽量去官网看)官网配置分为以下几个步骤1、 在conf文件目录下添加文件kafka_server_jaas.conf(文件目录 文件名随意)内容如下KafkaServer {org.apache.kafka.common.security.plain.PlainLoginModule requireduse…

oracle查询本月第一天_oracle获取本月第一天和最后一天及Oracle trunc()函数的用法...

select to_char(trunc(add_months(last_day(sysdate), -1) 1), yyyy-mm-dd) "本月第一天", to_char(last_day(sysdate), yyyy-mm-dd) "本月最后一天" --Oracle trunc()函数的用法 /**************日期********************/ 1.select trunc(sysdate) …