PriorityQueue原理分析——基于源码
在业务场景中,处理一个任务队列,可能需要依照某种优先级顺序,这时,Java中的PriorityQueue(优先队列)便可以派上用场。优先队列的原理与堆排序密不可分,可以参考我之前的一篇博客:
原理
PriorityQueue中维护一个Queue[]数组,在逻辑上把它理解成一个小根堆或大根堆,即一个完全二叉树,每一个三元组中父节点小于两个孩子结点(小根堆,如果是大于则是大根堆)。本博客以小根堆来进行说明,因为PriorityQueue默认实现小根堆,即小的数先出队,当然也可以自定义Comparator实现大根堆。
- 入队:每次入队时,把新元素挂在最后,从下往上遍历调整成小根堆;
- 出队:每次出队时,移除顶部元素,把最后的元素移到顶部,并从上往下遍历调整成小根堆。
出队
poll()方法如下:
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;
}
可以看到,队首元素 queue[0] 出队,队尾的元素 queue[s] 进入 siftDown(0, x) 方法进行堆调整。siftDown方法如下:
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
//k为开始遍历的位置,x为需要插入的值
@SuppressWarnings("unchecked")
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>)x;
int half = size >>> 1; // loop while a non-leaf
// 只需要遍历到数组的一半即可,保证遍历到最后一个三元组的父节点即可
while (k < half) {
int child = (k << 1) + 1; // assume left child is least
Object c = queue[child];
int right = child + 1;
if (right < size &&
((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
c = queue[child = right];//比较左右孩子结点,取最小的那个
if (key.compareTo((E) c) <= 0)
break;//找到了key应该放入的位置
queue[k] = c;
k = child;
}
queue[k] = key;
}
@SuppressWarnings("unchecked")
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}
可以看到,这与堆排序中的堆调整如出一辙。
入队
offer方法如下所示:
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1);
size = i + 1;
if (i == 0)
queue[0] = e;
else
siftUp(i, e);
return true;
}
同样,其核心在于 siftUp(i, e) 方法。如下所示:
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
@SuppressWarnings("unchecked")
private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
while (k > 0) {
int parent = (k - 1) >>> 1;//结点父节点的下标
Object e = queue[parent];
if (key.compareTo((E) e) >= 0)
break;//如果结点值大于父节点,则可以放置在该三元组下
queue[k] = e;//向子节点赋值父节点的值,不用担心某些值被覆盖,因为初始k等于size
k = parent;
}
queue[k] = key;//最后在待插入位置赋key的值
}
@SuppressWarnings("unchecked")
private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (comparator.compare(x, (E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = x;
}
此方法,是一个不断从父节点往子节点赋值的过程,直到找到适合放置插入结点值的位置。
移除
removeAt 方法如下所示:
private E removeAt(int i) {
// assert i >= 0 && i < size;
modCount++;
int s = --size;
if (s == i) // removed last element
queue[i] = null;
else {
E moved = (E) queue[s];
queue[s] = null;
siftDown(i, moved);
if (queue[i] == moved) {
siftUp(i, moved);
if (queue[i] != moved)
return moved;
}
}
return null;
}
移除下标为i的元素,相当于以 i 为根节点的完全二叉树的出队,于是执行 siftDown 方法调整最后一个元素 moved 的位置,即将该堆调整为小根堆。调整完之后,如果 moved 没有来到 i 的位置,说明 i 以上的堆结构一定符合规则;如果 moved 被调整到 i 位置,i上面的父节点有可能比 moved大,所以需要 siftUp(i, moved) 方法从 i 位置向上调整,调整为小根堆,完毕。
总结
其实不管是 siftUp 方法还是 siftDown 方法,都是利用了完全二叉树的性质,通过父节点与孩子结点之间的快速访问来实现的。
PriorityQueue原理分析——基于源码的更多相关文章
- 【源码分享】WPF漂亮界面框架实现原理分析及源码分享
1 源码下载 2 OSGi.NET插件应用架构概述 3 漂亮界面框架原理概述 4 漂亮界面框架实现 4.1 主程序 4.2 主程序与插件的通讯 4.2.1 主程序获取插件注册的服务 4.2 ...
- 小记--------spark的worker原理分析及源码分析
- uC/OS II原理分析及源码阅读(一)
uC/OS II(Micro Control Operation System Two)是一个可以基于ROM运行的.可裁减的.抢占式.实时多任务内核,具有高度可移植性,特别适合于微处理器和控制器,是和 ...
- 小记--------spark的Master主备切换机制原理分析及源码分析
aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAABfEAAAJwCAYAAAAp7ysfAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjw
- SpringMVC关于json、xml自动转换的原理研究[附带源码分析 --转
SpringMVC关于json.xml自动转换的原理研究[附带源码分析] 原文地址:http://www.cnblogs.com/fangjian0423/p/springMVC-xml-json-c ...
- 从壹开始微服务 [ DDD ] 之十一 ║ 基于源码分析,命令分发的过程(二)
缘起 哈喽小伙伴周三好,老张又来啦,DDD领域驱动设计的第二个D也快说完了,下一个系列我也在考虑之中,是 Id4 还是 Dockers 还没有想好,甚至昨天我还想,下一步是不是可以写一个简单的Angu ...
- 65、Spark Streaming:数据接收原理剖析与源码分析
一.数据接收原理 二.源码分析 入口包org.apache.spark.streaming.receiver下ReceiverSupervisorImpl类的onStart()方法 ### overr ...
- [源码分析] 从源码入手看 Flink Watermark 之传播过程
[源码分析] 从源码入手看 Flink Watermark 之传播过程 0x00 摘要 本文将通过源码分析,带领大家熟悉Flink Watermark 之传播过程,顺便也可以对Flink整体逻辑有一个 ...
- 助力SpringBoot自动配置的条件注解ConditionalOnXXX分析--SpringBoot源码(三)
注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 前言 本篇接 如何分析SpringBoot源码模块及结构?--SpringBoot源码(二) 上一篇分析了SpringBoo ...
随机推荐
- 利用 yum 命令和 rpm 命令升级 Nginx 或者安装最新版本 Nginx
方法一:使用 yum 命令升级 Nginx 1.在配置 YUM 仓库的目录(/etc/yum.repos.d/)下新增文件 nginx.repo vi /etc/yum.repos.d/nginx. ...
- chattr 和 lsattr 命令详解
lsattr 命令 lsattr 命令用于查看文件的第二扩展文件系统属性. 语法: lsattr(选项)(参数) 选项: -E:可显示设备属性的当前值,但这个当前值是从用户设备数据库中获得的,而不是从 ...
- Mac zsh中所有命令失效
参考文章 https://blog.csdn.net/hujincai_55/article/details/95680245?utm_medium=distribute.pc_relevant.no ...
- day08 Pyhton学习
一.昨日内容回顾 .1.基础部分的补充 join() 把列表变成字符串, 拼接 split() 切割 删除: 列表和字典不能在循环的时候进行删除. 把要删除的内容记录在一个新列表中,然后循环新列表, ...
- IDEA SpringBoot+JPA+MySql+Redis+RabbitMQ 秒杀系统
先放上github地址:spike-system,可以直接下载完整项目运行测试 SpringBoot+JPA+MySql+Redis+RabbitMQ 秒杀系统 技术栈:SpringBoot, MyS ...
- 【贪心算法】HDU 5969 最大的位或
题目内容 Vjudge链接 给出一个闭区间,找该区间内两个数,使这两个数的按位或最大. 输入格式 包含至多\(10001\)组测试数据. 第一行有一个正整数,表示数据的组数. 接下来每一行表示一组数据 ...
- Golang数组和切片的区别
大纲 数组是固定大小 切片不是动态数组,可以扩容 区别 定义方式不一样 初始化方法不一样 package main import "fmt" func main() { // -- ...
- linux(centos8):firewalld的运行时日志配置
一,firewalld配置日志的用途: 在生产环境中,firewalld的默认配置是不记录日志 我们通过日志记录下防火墙过滤时拒绝的非法ip, 可以主动把这些有攻击性的ip加入到黑名单, 防患于未然 ...
- Archery安装教程
一. CentOS设置 1. 更换阿里源 curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos ...
- Linux文件的查找之find命令处理动作
查找到文件之后的处理动作 例如:找出来系统中比较大超过10G的并且存放时间超过一年的log文件并删除 find / -name ".log" -size +10G -mtime + ...