PriorityQueue分析
概述
PriorityQueue,优先级队列,一种特殊的队列,作用是能保证每次取出的元素都是队列中权值最小的(Java的优先队列每次取最小元素,C++的优先队列每次取最大元素)。元素大小的评判可以通过元素本身的自然顺序(Natural Ordering),也可通过构造时传入的比较器(如Java中的Comparator,C++的仿函数)。
Java中PriorityQueue通过二叉小顶堆实现,可用一棵完全二叉树表示。PriorityQueue实现Queue接口,不允许放入null元素;其通过堆实现,具体说是通过完全二叉树(Complete Binary Tree)实现的小顶堆(任意一个非叶子节点的权值,都不大于其左右子节点的权值),也就意味着可以通过数组来作为PriorityQueue的底层实现。

给每个元素按照层序遍历的方式进行编号,父子节点的编号之间的关系:
leftNo = parentNo*2+1
rightNo = parentNo*2+2
parentNo = (nodeNo-1)/2
通过上述三个公式,可以轻易计算出某个节点的父节点以及子节点的下标。这也就是为什么可以直接用数组来存储堆的原因。
应用场景
常用于需要按优先级处理元素的场景:
- 任务调度:调度系统中的任务可能有不同的优先级。使用PriorityQueue可确保高优先级的任务先执行。JDK中的定时任务调度可能会使用类似的结构来管理不同的任务;
- 图算法:许多图算法(如Dijkstra)需要频繁选择当前最小(或最大)权重的边或节点。PriorityQueue适合存储这些节点,以快速获取最优解;
- 实时数据处理:在一些流式处理框架中,可能需要实时处理数据并保持数据的优先级。PriorityQueue可帮助维护这些数据的顺序;
- MQ:在某些消息中间件中,可能会根据消息的优先级来处理消息。使用PriorityQueue可实现这一点,确保高优先级的消息先被消费;
- 事件驱动架构:在事件驱动系统中,基于事件优先级,PriorityQueue可方便地管理和调度这些事件;
- 合并多个有序序列:在处理多个已排序的数据流时,可使用PriorityQueue来合并这些序列,以保持整体的排序。
源码
PriorityQueue的peek()和element操作是常数时间,add(),offer(),无参数的remove()以及poll()方法的时间复杂度都是log(N)。
方法剖析
add()和offer()
都是向优先队列中插入元素,Queue接口规定二者对插入失败时的处理不同,前者在插入失败时抛出异常,后者则会返回false。
新加入的元素可能会破坏小顶堆的性质,因此需要进行必要的调整:
public boolean offer(E e) {if (e == null)throw new NullPointerException();modCount++;int i = size;if (i >= queue.length)grow(i + 1);siftUp(i, e);size = i + 1;return true;
}
扩容函数grow()类似于ArrayList里的grow(),再申请一个更大的数组,并将原数组的元素复制过去。siftUp(int k, E x)方法用于插入元素x并维持堆的特性。
private void siftUp(int k, E x) {if (comparator != null)siftUpUsingComparator(k, x, queue, comparator);elsesiftUpComparable(k, x, queue);
}private static <T> void siftUpComparable(int k, T x, Object[] es) {Comparable<? super T> key = (Comparable<? super T>) x;while (k > 0) {int parent = (k - 1) >>> 1;Object e = es[parent];if (key.compareTo((T) e) >= 0)break;es[k] = e;k = parent;}es[k] = key;
}private static <T> void siftUpUsingComparator(int k, T x, Object[] es, Comparator<? super T> cmp) {while (k > 0) {int parent = (k - 1) >>> 1;Object e = es[parent];if (cmp.compare(x, (T) e) >= 0)break;es[k] = e;k = parent;}es[k] = x;
}
调整过程:从k指定的位置开始,将x逐层与当前点的parent进行比较并交换,直到满足x >= queue[parent]为止。比较可以是元素的自然顺序,也可以是依靠比较器的顺序。
element()和peek()
语义完全相同,都是获取但不删除队首元素,也就是队列中权值最小的那个元素,唯一的区别是当方法失败时前者抛出异常,后者返回null。根据小顶堆的性质,堆顶那个元素就是全局最小的那个;由于堆用数组表示,根据下标关系,0下标处的那个元素既是堆顶元素。所以直接返回数组0下标处的那个元素即可。
代码:
public E peek() {return (E) queue[0];
}
remove()和poll()
语义完全相同,都是获取并删除队首元素,区别是当方法失败时前者抛出异常,后者返回null。由于删除操作会改变队列的结构,为维护小顶堆的性质,需要进行必要的调整。
public E poll() {final Object[] es;final E result;if ((result = (E) ((es = queue)[0])) != null) {modCount++;final int n;final E x = (E) es[(n = --size)];es[n] = null;if (n > 0) {final Comparator<? super E> cmp;if ((cmp = comparator) == null)siftDownComparable(0, x, es, n);elsesiftDownUsingComparator(0, x, es, n, cmp);}}return result;
}
首先记录0下标处的元素,并用最后一个元素替换0下标位置的元素,之后调用siftDown()方法对堆进行调整,最后返回原来0下标处的那个元素(也就是最小的那个元素)。siftDown(int k, E x)方法从k指定的位置开始,将x逐层向下与当前点的左右孩子中较小的那个交换,直到x小于或等于左右孩子中的任何一个为止。
private void siftDown(int k, E x) {if (comparator != null)siftDownUsingComparator(k, x, queue, size, comparator);elsesiftDownComparable(k, x, queue, size);
}private static <T> void siftDownComparable(int k, T x, Object[] es, int n) {// assert n > 0;Comparable<? super T> key = (Comparable<? super T>)x;int half = n >>> 1; // loop while a non-leafwhile (k < half) {int child = (k << 1) + 1; // assume left child is leastObject c = es[child];int right = child + 1;if (right < n && ((Comparable<? super T>) c).compareTo((T) es[right]) > 0)c = es[child = right];if (key.compareTo((T) c) <= 0)break;es[k] = c;k = child;}es[k] = key;
}private static <T> void siftDownUsingComparator(int k, T x, Object[] es, int n, Comparator<? super T> cmp) {// assert n > 0;int half = n >>> 1;while (k < half) {int child = (k << 1) + 1;Object c = es[child];int right = child + 1;if (right < n && cmp.compare((T) c, (T) es[right]) > 0)c = es[child = right];if (cmp.compare(x, (T) c) <= 0)break;es[k] = c;k = child;}es[k] = x;
}
remove(Object o)
用于删除队列中跟o相等的某一个元素(如果有多个相等,只删除一个),源自Collection接口的方法。由于删除操作会改变队列结构,所以要进行调整;又由于删除元素的位置可能是任意的,所以调整过程比其它函数稍加繁琐。具体来说,remove(Object o)可分为2种情况:
- 删除的是最后一个元素。直接删除即可,不需调整;
- 不是最后一个元素,从删除点开始以最后一个元素为参照调用一次siftDown()即可。???
public boolean remove(Object o) {// 通过遍历数组的方式找到第一个满足o.equals(queue[i])元素的下标int i = indexOf(o);if (i == -1)return false;else {removeAt(i);return true;}
}
private int indexOf(Object o) {if (o != null) {final Object[] es = queue;for (int i = 0, n = size; i < n; i++)if (o.equals(es[i]))return i;}return -1;
}
E removeAt(int i) {// assert i >= 0 && i < size;final Object[] es = queue;modCount++;int s = --size;if (s == i) // removed last elementes[i] = null;else {E moved = (E) es[s];es[s] = null;siftDown(i, moved);if (es[i] == moved) {siftUp(i, moved);if (es[i] != moved)return moved;}}return null;
}
堆排序
堆排序利用堆的特性来排序一个数组,通常包含以下几个步骤:
- 构建最大堆
将输入数据构建成一个最大堆,从最后一个非叶子节点开始,逐层向上调整堆,确保每个父节点都大于或等于子节点。 - 排序过程
- 将最大堆的根节点(最大值)与堆的最后一个元素交换,然后将堆的大小减一(实际上是忽略最后一个元素);
- 对新的根节点进行堆调整,重新建立最大堆;
- 重复上述过程,直到堆的大小为1。
示例代码:
public class HeapSort {public static void heapSort(int[] arr) {PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);for (int num : arr) {maxHeap.offer(num);}for (int i = 0; i < arr.length; i++) {arr[i] = maxHeap.poll();}}public static void main(String[] args) {int[] arr = {4, 10, 3, 5, 1};heapSort(arr);for (int num : arr) {System.out.print(num + " ");}}
}
拓展
JDK
JDK源码里基于PriorityQueue实现的数据结构主要有两个:
- PriorityBlockingQueue:带优先级的阻塞队列
- DelayQueue:延迟队列
PriorityBlockingQueue
在线程池里看到这个类,一个支持优先级排序的无界阻塞队列。PriorityBlockingQueue不会阻塞生产者,而只是在没有可消费的数据时阻塞消费者。因此使用时需要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,最终会耗尽所有可用的内存空间。
PriorityBlockingQueue的几个属性:
- queue:数组,用来存放队列元素;
- size:用来存放队列元素个数;
- lock:独占锁对象,用来控制某个时间只能有一个线程可以进行入队、出队操作
- notEmpty:条件(Condition)变量,用来实现take方法阻塞模式(跟其它阻塞队列相比,这里没有notFull条件变量,这是因为PriorityBlockingQueue是无界队列,其put方法是非阻塞的)。
- allocationspinLock:是个自旋锁,使用CAS操作来保证某个时间只有一个线程可以扩容队列,状态为0或1,0表示当前没有进行扩容,1表示当前正在扩容;
PriorityBlockingQueue内部是使用平衡二叉树堆实现的,所以直接遍历队列元素不保证元素有序。
每次出队都返回优先级最高或最低的元素,默认使用对象的compareTo方法提供比较规则,这意味着队列元素必须实现Comparable接口;如果需要自定义比较规则则可以通过构造函数自定义comparator。
DelayQueue
ScheduledExecutorService
ScheduledExecutorService是JDK提供的用于定时任务调度API,支持设置任务优先级:
private ScheduledExecutorService schdExctr = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {private ThreadFactory factory = Executors.defaultThreadFactory();@Overridepublic Thread newThread(Runnable r) {Thread t = factory.newThread(r);t.setPriority(Thread.MIN_PRIORITY);return t;}
});
RocketMQ消息优先级
RocketMQ通过消息优先级实现消息的先入先出。消息优先级由生产者在发送消息时设置,范围为0-4,数字越大优先级越高。
Broker在接收到消息后,会将消息存储在不同的队列中,每种优先级对应一个队列。Broker会按照优先级从高到低的顺序消费队列中的消息,实现高优先级消息的先消费。
消费者在消费消息时,也会按照优先级从高到低的顺序拉取队列中的消息进行消费,保证高优先级消息的先消费。
RocketMQ通过设置消息优先级和隔离优先级消息到不同队列来实现消息的优先级,从而达到高优先级消息的先入先出。
仅供参考的代码:
// 生产者发送高优先级消息
Message msg = new Message("TopicTest", "TagA", "KEY", "Hello".getBytes());
msg.setKeys("KEY1");
msg.setPriority(3); // 设置消息优先级为3
producer.send(msg);// 消费者设置消费消息的优先级
pullConsumer.setConsumeMessageAck(true);
pullConsumer.registerMessageListener(new PriorityMessageListener());public class PriorityMessageListener implements MessageListener {@Overridepublic Action consumeMessage(MessageExt ext) { switch (ext.getPriority()) {case 3: // 消费优先级为3的消息 break;}}
}
参考
相关文章:
PriorityQueue分析
概述 PriorityQueue,优先级队列,一种特殊的队列,作用是能保证每次取出的元素都是队列中权值最小的(Java的优先队列每次取最小元素,C的优先队列每次取最大元素)。元素大小的评判可以通过元素本身的自然顺序…...
Hive数仓操作(六)
一、 Hive 分区表 Hive 的分区表通过在 HDFS 中以不同的目录存储不同的分区数据,来提高查询性能并减少数据扫描量。分区表可以根据特定的列(如 性别 列的男/女)将数据划分为多个部分,使得查询时只需要扫描相关的分区,…...
centos7安装配置python3环境
1、wget https://www.python.org/ftp/python/3.11.2/Python-3.11.2.tgz 2、安装python依赖环境 切换到root用户,然后执行下面命令: 3、安装gcc,用于后续安装Python时编译源码: yum install gcc -y 4、安装Python3相关依赖&#…...
用 LoRA 微调 Stable Diffusion:拆开炼丹炉,动手实现你的第一次 AI 绘画
总得拆开炼丹炉看看是什么样的。这篇文章将带你从代码层面一步步实现 AI 文本生成图像(Text-to-Image)中的 LoRA 微调过程,你将: 了解 Trigger Words(触发词)到底是什么,以及它们如何影响生成结…...
手机实时提取SIM卡打电话的信令声音-(题外、插播一条广告)
手机实时提取SIM卡打电话的信令声音-(题外、插播一条广告) 前言 在去年的差不多这个时候,我们做了一遍外置配件的选型,筛选过滤了一批USB蓝牙配件和type-c转usb的模块。详情可参考《外置配件的电商价格和下载链接的选型.docx》一文:蓝牙电话…...
Linux基于CentOS学习【进程状态】【进程优先级】【调度与切换】【进程挂起】【进程饥饿】
目录 进程状态 状态决定了什么 进程等待方式——队列 进程状态的表现 挂起状态 基于阻塞的挂起——阻塞挂起 swap分区 进程状态表示 Z僵尸状态 进程的优先级 什么是进程的优先级 为什么会有进程的优先级 进程饥饿 Linux的调度与切换 切换 调度 queue [ 140 ]&am…...
Golang | Leetcode Golang题解之第456题132模式
题目: 题解: func find132pattern(nums []int) bool {candidateI, candidateJ : []int{-nums[0]}, []int{-nums[0]}for _, v : range nums[1:] {idxI : sort.SearchInts(candidateI, 1-v)idxJ : sort.SearchInts(candidateJ, -v)if idxI < idxJ {ret…...
回归预测|基于哈里斯鹰优化最小二乘支持向量机的数据回归预测Matlab程序HHO-LSSVM 多特征输入单输出含基础程序
回归预测|基于哈里斯鹰优化最小二乘支持向量机的数据回归预测Matlab程序HHO-LSSVM 多特征输入单输出含基础程序 文章目录 一、基本原理一、基本原理二、HHO-LSSVM的流程三、优缺点四、应用场景 二、实验结果三、核心代码四、代码获取五、总结 一、基本原理 HHO-LSSVM回归预测结…...
【Android 源码分析】Activity生命周期之onStop-1
忽然有一天,我想要做一件事:去代码中去验证那些曾经被“灌输”的理论。 – 服装…...
【Unity】本地化实现
个人向笔记。 1 前言 记录一下自己的本地化实现思路,暂时只讲本文的本地化实现。 2 文本本地化方案-个人 本地化实现是基于Luban的。自己使用Luban实现了一个“配置表模块”,又实现了一个“全局配置模块”,之后再基于这两个模块实现了“文本…...
Django一分钟:在Django中怎么存储树形结构的数据,DRF校验递归嵌套模型的替代方案
引言 在开发过程中我们可能需要这样的树形结构: [{"data": {"name": "牛奶"},"children": [{"data": {"name": "蒙牛"}, },{"data": {"name": "伊利"}, }]},{"da…...
【Docker从入门到进阶】06.常见问题与解决方案 07.总结与资源
6. 常见问题与解决方案 在使用Docker进行开发和部署过程中,可能会遇到各种问题。以下是一些常见问题及其解决方案: 容器启动失败和调试 在使用 Docker 时,容器启动失败或立即退出可能会导致一定的困扰,以下是进一步深入解决该问…...
快速排序的非递归实现:借助栈实现、借助队列实现
目录 用栈实现快速排序 1.用栈实现非递归快速排序的思路步骤 1.1.思路步骤 2.用栈实现非递归快速排序的代码 3.用栈实现非递归快速排序的整个工程 3.1.QuickSortNonR.h 3.2.QuickSortNonR.c 3.3.Stack.h 3.4.Stack.c 用队列实现非递归快速排序 1.用队列实现非递归快…...
Finops成本优化企业实践-可视化篇
引言:上一章讨论了finops的一些方法论,笔者在拿到finops官方认证finops-engineer certificate之后,将方法论运用到所在项目组中,并于今年完成了40%的费用节省。在此将这些实践方法总结沉淀,与大家分享。实践包括三篇&a…...
Spring Boot中线程池使用
说明:在一些场景,如导入数据,批量插入数据库,使用常规方法,需要等待较长时间,而使用线程池可以提高效率。本文介绍如何在Spring Boot中使用线程池来批量插入数据。 搭建环境 首先,创建一个Spr…...
Python机器学习:自然语言处理、计算机视觉与强化学习
📘 Python机器学习:自然语言处理、计算机视觉与强化学习 目录 ✨ 自然语言处理(NLP) 文本预处理:分词、去停用词词向量与文本分类:使用Word2Vec与BERT 🌆 计算机视觉基础 图像预处理与增强目标…...
Vue2 + ElementUI + axios + VueRouter入门
之前没有pc端开发基础,工作需要使用若依框架进行了一年的前端开发.最近看到一个视频框架一步步集成,感觉颇受启发,在此记录一下学习心得。视频链接:vue2element ui 快速入门 环境搭建和依赖安装 安装nodejs安装Vue Cli使用vue create proje…...
GO网络编程(四):海量用户通信系统2:登录功能核心【重难点】
目录 一、C/S详细通信流程图二、消息类型定义与json标签1. 消息类型定义2. JSON标签3.结构体示例及其 JSON 表示:4.完整代码与使用说明 三、客户端发送消息1. 连接到服务器2. 准备发送消息3. 创建 LoginMes 并序列化4. 将序列化后的数据嵌入消息结构5. 序列化整个 M…...
某项目实战分析代码二
某项目实战分析代码二 此次分析的是protobuf的使用操作流程具体实现 3. 业务数据分析3.1 客户端3.2 服务器端简单案例 此次分析的是protobuf的使用 Protocol Buffer( 简称 Protobuf) 是Google公司内部的混合语言数据标准,它是一种轻便高效的结构化数据存储格式&…...
全面指南:探索并实施解决Windows系统中“mfc140u.dll丢失”的解决方法
当你的电脑出现mfc140u.dll丢失的问题是什么情况呢?mfc140u.dll文件依赖了什么?mfc140u.dll丢失会导致电脑出现什么情况?今天这篇文章就和大家聊聊mfc140u.dll丢失的解决办法。希望能够有效的帮助你解决这问题。 哪些程序依赖mfc140u.dll文件…...
从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...
centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...
(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...
关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...
sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!
简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求,并检查收到的响应。它以以下模式之一…...
用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...
