在业务场景中,处理一个任务队列,可能需要依照某种优先级顺序,这时,Java中的PriorityQueue(优先队列)便可以派上用场。优先队列的原理与堆排序密不可分,可以参考我之前的一篇博客:

堆排序总结与实现

原理

PriorityQueue中维护一个Queue[]数组,在逻辑上把它理解成一个小根堆或大根堆,即一个完全二叉树,每一个三元组中父节点小于两个孩子结点(小根堆,如果是大于则是大根堆)。本博客以小根堆来进行说明,因为PriorityQueue默认实现小根堆,即小的数先出队,当然也可以自定义Comparator实现大根堆。

  • 入队:每次入队时,把新元素挂在最后,从下往上遍历调整成小根堆;
  • 出队:每次出队时,移除顶部元素,把最后的元素移到顶部,并从上往下遍历调整成小根堆。

出队

poll()方法如下:

  1. public E poll() {
  2. if (size == 0)
  3. return null;
  4. int s = --size;
  5. modCount++;
  6. E result = (E) queue[0];
  7. E x = (E) queue[s];
  8. queue[s] = null;
  9. if (s != 0)
  10. siftDown(0, x);
  11. return result;
  12. }

可以看到,队首元素 queue[0] 出队,队尾的元素 queue[s] 进入 siftDown(0, x) 方法进行堆调整。siftDown方法如下:

  1. private void siftDown(int k, E x) {
  2. if (comparator != null)
  3. siftDownUsingComparator(k, x);
  4. else
  5. siftDownComparable(k, x);
  6. }
  7. //k为开始遍历的位置,x为需要插入的值
  8. @SuppressWarnings("unchecked")
  9. private void siftDownComparable(int k, E x) {
  10. Comparable<? super E> key = (Comparable<? super E>)x;
  11. int half = size >>> 1; // loop while a non-leaf
  12. // 只需要遍历到数组的一半即可,保证遍历到最后一个三元组的父节点即可
  13. while (k < half) {
  14. int child = (k << 1) + 1; // assume left child is least
  15. Object c = queue[child];
  16. int right = child + 1;
  17. if (right < size &&
  18. ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
  19. c = queue[child = right];//比较左右孩子结点,取最小的那个
  20. if (key.compareTo((E) c) <= 0)
  21. break;//找到了key应该放入的位置
  22. queue[k] = c;
  23. k = child;
  24. }
  25. queue[k] = key;
  26. }
  27. @SuppressWarnings("unchecked")
  28. private void siftDownUsingComparator(int k, E x) {
  29. int half = size >>> 1;
  30. while (k < half) {
  31. int child = (k << 1) + 1;
  32. Object c = queue[child];
  33. int right = child + 1;
  34. if (right < size &&
  35. comparator.compare((E) c, (E) queue[right]) > 0)
  36. c = queue[child = right];
  37. if (comparator.compare(x, (E) c) <= 0)
  38. break;
  39. queue[k] = c;
  40. k = child;
  41. }
  42. queue[k] = x;
  43. }

可以看到,这与堆排序中的堆调整如出一辙。

入队

offer方法如下所示:

  1. public boolean offer(E e) {
  2. if (e == null)
  3. throw new NullPointerException();
  4. modCount++;
  5. int i = size;
  6. if (i >= queue.length)
  7. grow(i + 1);
  8. size = i + 1;
  9. if (i == 0)
  10. queue[0] = e;
  11. else
  12. siftUp(i, e);
  13. return true;
  14. }

同样,其核心在于 siftUp(i, e) 方法。如下所示:

  1. private void siftUp(int k, E x) {
  2. if (comparator != null)
  3. siftUpUsingComparator(k, x);
  4. else
  5. siftUpComparable(k, x);
  6. }
  7. @SuppressWarnings("unchecked")
  8. private void siftUpComparable(int k, E x) {
  9. Comparable<? super E> key = (Comparable<? super E>) x;
  10. while (k > 0) {
  11. int parent = (k - 1) >>> 1;//结点父节点的下标
  12. Object e = queue[parent];
  13. if (key.compareTo((E) e) >= 0)
  14. break;//如果结点值大于父节点,则可以放置在该三元组下
  15. queue[k] = e;//向子节点赋值父节点的值,不用担心某些值被覆盖,因为初始k等于size
  16. k = parent;
  17. }
  18. queue[k] = key;//最后在待插入位置赋key的值
  19. }
  20. @SuppressWarnings("unchecked")
  21. private void siftUpUsingComparator(int k, E x) {
  22. while (k > 0) {
  23. int parent = (k - 1) >>> 1;
  24. Object e = queue[parent];
  25. if (comparator.compare(x, (E) e) >= 0)
  26. break;
  27. queue[k] = e;
  28. k = parent;
  29. }
  30. queue[k] = x;
  31. }

此方法,是一个不断从父节点往子节点赋值的过程,直到找到适合放置插入结点值的位置。

移除

removeAt 方法如下所示:

  1. private E removeAt(int i) {
  2. // assert i >= 0 && i < size;
  3. modCount++;
  4. int s = --size;
  5. if (s == i) // removed last element
  6. queue[i] = null;
  7. else {
  8. E moved = (E) queue[s];
  9. queue[s] = null;
  10. siftDown(i, moved);
  11. if (queue[i] == moved) {
  12. siftUp(i, moved);
  13. if (queue[i] != moved)
  14. return moved;
  15. }
  16. }
  17. return null;
  18. }

移除下标为i的元素,相当于以 i 为根节点的完全二叉树的出队,于是执行 siftDown 方法调整最后一个元素 moved 的位置,即将该堆调整为小根堆。调整完之后,如果 moved 没有来到 i 的位置,说明 i 以上的堆结构一定符合规则;如果 moved 被调整到 i 位置,i上面的父节点有可能比 moved大,所以需要 siftUp(i, moved) 方法从 i 位置向上调整,调整为小根堆,完毕。

总结

其实不管是 siftUp 方法还是 siftDown 方法,都是利用了完全二叉树的性质,通过父节点与孩子结点之间的快速访问来实现的。

PriorityQueue原理分析——基于源码的更多相关文章

  1. 【源码分享】WPF漂亮界面框架实现原理分析及源码分享

    1 源码下载 2 OSGi.NET插件应用架构概述 3 漂亮界面框架原理概述 4 漂亮界面框架实现  4.1 主程序  4.2 主程序与插件的通讯   4.2.1 主程序获取插件注册的服务   4.2 ...

  2. 小记--------spark的worker原理分析及源码分析

     

  3. uC/OS II原理分析及源码阅读(一)

    uC/OS II(Micro Control Operation System Two)是一个可以基于ROM运行的.可裁减的.抢占式.实时多任务内核,具有高度可移植性,特别适合于微处理器和控制器,是和 ...

  4. 小记--------spark的Master主备切换机制原理分析及源码分析

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAABfEAAAJwCAYAAAAp7ysfAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjw

  5. SpringMVC关于json、xml自动转换的原理研究[附带源码分析 --转

    SpringMVC关于json.xml自动转换的原理研究[附带源码分析] 原文地址:http://www.cnblogs.com/fangjian0423/p/springMVC-xml-json-c ...

  6. 从壹开始微服务 [ DDD ] 之十一 ║ 基于源码分析,命令分发的过程(二)

    缘起 哈喽小伙伴周三好,老张又来啦,DDD领域驱动设计的第二个D也快说完了,下一个系列我也在考虑之中,是 Id4 还是 Dockers 还没有想好,甚至昨天我还想,下一步是不是可以写一个简单的Angu ...

  7. 65、Spark Streaming:数据接收原理剖析与源码分析

    一.数据接收原理 二.源码分析 入口包org.apache.spark.streaming.receiver下ReceiverSupervisorImpl类的onStart()方法 ### overr ...

  8. [源码分析] 从源码入手看 Flink Watermark 之传播过程

    [源码分析] 从源码入手看 Flink Watermark 之传播过程 0x00 摘要 本文将通过源码分析,带领大家熟悉Flink Watermark 之传播过程,顺便也可以对Flink整体逻辑有一个 ...

  9. 助力SpringBoot自动配置的条件注解ConditionalOnXXX分析--SpringBoot源码(三)

    注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 前言 本篇接 如何分析SpringBoot源码模块及结构?--SpringBoot源码(二) 上一篇分析了SpringBoo ...

随机推荐

  1. ✅Vue选择图像

    下载 Vue选择图像Vue选择图像 Vue 2.用于从列表中选择图像的组件 演示 https://mazipan.github.io/vue-select-image/ 安装 #纱 纱添加vue-se ...

  2. Android和。net加密。

    来源: Github: https://github.com/Pavel-Durov/CodeProject-Android-and-NET-Encryption 直接: Source Code (A ...

  3. How to install the NVIDIA drivers on Fedora 32

    https://linuxconfig.org/how-to-install-the-nvidia-drivers-on-fedora-32 The NVIDIA Driver is a progra ...

  4. Linux下clock子系统

    常用API: 1.struct clk *clk_get(struct device *dev, const char *id):从一个时钟list链表中以dev或者字符id名称查找一个时钟clk结构 ...

  5. 多测师讲解html _有序列表005_高级讲师肖sir

    <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>有 ...

  6. Centos6.X 手动升级gcc

    操作环境 CentOS6.5 64bit,gcc原版本为4.4.7,不能支持C++11的特性,所以需要升级 [root@zengxj ~]# wget http://ftp.gnu.org/gnu/g ...

  7. spring-boot-route(十九)spring-boot-admin监控服务

    SpringBootAdmin不是Spring官方提供的模块,它包含了Client和Server两部分.server部分提供了用户管理界面,client即为被监控的服务.client需要注册到serv ...

  8. Pytest之使用断言指定异常

    官网的翻译是使用断言抛出指定异常,当我觉得他这里更应该指的是 Pytest 断言错误类型# 使用raise在测试方法中指定异常的类型,这点和java还是蛮像的呢,具体示例如下: import pyte ...

  9. cve-2020-1472,netlogon特权提升漏洞复现

    cve-2020-1472,netlogon特权提升漏洞, 漏洞原理:攻击者通过NetLogon(MS-NRPC),建立与域控间易受攻击的安全通道时,可利用此漏洞获取域管访问权限.成功利用此漏洞的攻击 ...

  10. 使用pyenv实现python多版本共存

    背景 如果是Ubuntu等桌面系统,都已经更新到了Python较新的版本.但多数生产环境使用的还是红帽系统. CentOS7默认还是Python2.7,而开发环境如果是高版本Python就带来了问题. ...