1. PriorityQueue是什么

是一个队列,只不过加上了优先级的概念,换句话说队列里的元素是根据某种规则排好序的

2. 使用

public class PriorityQueueTest
{
public static void main(String[] args)
{
List<Integer> list = Arrays.asList(20, 19, 18, 17, 16, 15, 14, 13, 12, 11);
PriorityQueue<Integer> queue = new PriorityQueue<>(list);
System.out.println("最开始的元素:" + queue); System.out.println("============");
queue.offer(11);
System.out.println("添加了一个11:" + queue); System.out.println("============");
List<Integer> sort = new ArrayList<>();
while (!queue.isEmpty())
{
Integer poll = queue.poll();
System.out.println("堆顶是:" + poll);
sort.add(poll);
System.out.println("剩余的元素:" + queue);
System.out.println("----------");
} System.out.println("============");
System.out.println("堆排序的结果:" + sort); }
}
  • 输出
最开始的元素:[11, 12, 14, 13, 16, 15, 18, 20, 17, 19]
============
添加了一个11:[11, 11, 14, 13, 12, 15, 18, 20, 17, 19, 16]
============
堆顶是:11
剩余的元素:[11, 12, 14, 13, 16, 15, 18, 20, 17, 19]
----------
堆顶是:11
剩余的元素:[12, 13, 14, 17, 16, 15, 18, 20, 19]
----------
堆顶是:12
剩余的元素:[13, 16, 14, 17, 19, 15, 18, 20]
----------
堆顶是:13
剩余的元素:[14, 16, 15, 17, 19, 20, 18]
----------
堆顶是:14
剩余的元素:[15, 16, 18, 17, 19, 20]
----------
堆顶是:15
剩余的元素:[16, 17, 18, 20, 19]
----------
堆顶是:16
剩余的元素:[17, 19, 18, 20]
----------
堆顶是:17
剩余的元素:[18, 19, 20]
----------
堆顶是:18
剩余的元素:[19, 20]
----------
堆顶是:19
剩余的元素:[20]
----------
堆顶是:20
剩余的元素:[]
----------
============
堆排序的结果:[11, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] Process finished with exit code 0

3. 源码分析

3.1. 属性

public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable { private static final long serialVersionUID = -7720805057305804111L; //默认的大小为11
private static final int DEFAULT_INITIAL_CAPACITY = 11; //二叉堆:物理上是个数组,逻辑上看成二叉树
transient Object[] queue; // non-private to simplify nested class access //容量
private int size = 0; //比较器,用来确定插入的元素在二叉堆中的位置
private final Comparator<? super E> comparator; //无参构造
public PriorityQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
} }

3.2. 有参构造

//有参构造方法。我们主要分析这个
public PriorityQueue(Collection<? extends E> c) {
if (c instanceof SortedSet<?>) {
SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
this.comparator = (Comparator<? super E>) ss.comparator();
initElementsFromCollection(ss);
}
else if (c instanceof PriorityQueue<?>) {
PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c;
this.comparator = (Comparator<? super E>) pq.comparator();
initFromPriorityQueue(pq);
}
//传入的List走的这个else逻辑
else {
this.comparator = null;
initFromCollection(c);
}
  • initFromCollection
private void initFromCollection(Collection<? extends E> c) {
initElementsFromCollection(c);
heapify();
}

可以看出主要有两个步骤,一个是建立初始化元素到数组中,另一个是维护堆的属性

3.2.1. 初始化元素到数组中

  • initElementsFromCollection
private void initElementsFromCollection(Collection<? extends E> c) {
//先转成数组,如果数组不是Object[]类型的,那么需要转成Object[]数组
//我们的是Integer数组,所以会走这段逻辑copy成Object数组
Object[] a = c.toArray();
if (a.getClass() != Object[].class)
a = Arrays.copyOf(a, a.length, Object[].class);
int len = a.length;
//元素不能有为null的
if (len == 1 || this.comparator != null)
for (int i = 0; i < len; i++)
if (a[i] == null)
throw new NullPointerException();
//对queue和size赋值,很普通
this.queue = a;
this.size = a.length;
}

3.2.2. 维护堆的属性

  • heapify
private void heapify() {
//i会初始化为最后一个有左孩子或者右孩子后者两者都有的 非叶子节点的下标
//调用下沉操作维护堆的属性
//一直到头节点为止(i--)
for (int i = (size >>> 1) - 1; i >= 0; i--)
//位置i的元素为queue[i],对其执行下沉操作
siftDown(i, (E) queue[i]);
}
  • siftDown
//数组中位置k的元素为x,对其执行下沉操作,维护这棵子树的堆属性
private void siftDown(int k, E x) {
//如果设置了自定义的comparator,那么使用之
if (comparator != null)
siftDownUsingComparator(k, x);
//我们没设置,那么走这一段
else
siftDownComparable(k, x);
}
3.2.2.1. 下沉操作

private void siftDownComparable(int k, E x) {
//必须实现了Comparable接口
Comparable<? super E> key = (Comparable<? super E>)x;
//数组的一半大小,这个是下沉操作终止的条件
//即一直往下调整到叶子节点为止
int half = size >>> 1;
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];
//c为左右孩子较小的那个,跟父亲(我自己)比,如果父亲较小,没必要调整,直接break退出
if (key.compareTo((E) c) <= 0)
break;
//否则把较小的孩子覆盖到父亲的位置
queue[k] = c;
//把较小的孩子作为下一个父亲,继续执行下沉操作
k = child;
}
//把key(其实就是x)放在他该在的位置
queue[k] = key;
}

3.3. 插入

  • 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
//数组中位置k的元素为x,对其执行上浮操作,维护这棵子树的堆属性
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
//没有comparator走这段
else
siftUpComparable(k, x);
}

3.3.1. 上浮操作

private void siftUpComparable(int k, E x) {
//必须实现Comparable接口
Comparable<? super E> key = (Comparable<? super E>) x;
while (k > 0) {
//获取父亲
int parent = (k - 1) >>> 1;
Object e = queue[parent];
//如果我比父亲大,那么不需要调整,直接break退出
if (key.compareTo((E) e) >= 0)
break;
//把父亲覆盖到我的位置
queue[k] = e;
//把父亲作为下一个节点,继续执行上浮操作
k = parent;
}
//把key(其实就是x)放在他该在的位置
queue[k] = key;
}

3.4. 删除

  • poll
public E poll() {
//一个元素都没有那么返回null
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;
}
  • siftDown
//数组中位置k的元素为x,对其执行下沉操作,维护这棵子树的堆属性
private void siftDown(int k, E x) {
//如果设置了自定义的comparator,那么使用之
if (comparator != null)
siftDownUsingComparator(k, x);
//我们没设置,那么走这一段
else
siftDownComparable(k, x);
}

3.4.1. 下沉操作


private void siftDownComparable(int k, E x) {
//必须实现了Comparable接口
Comparable<? super E> key = (Comparable<? super E>)x;
//数组的一半大小,这个是下沉操作终止的条件
//即一直往下调整到叶子节点为止
int half = size >>> 1;
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];
//c为左右孩子较小的那个,跟父亲(我自己)比,如果父亲较小,没必要调整,直接break退出
if (key.compareTo((E) c) <= 0)
break;
//否则把较小的孩子覆盖到父亲的位置
queue[k] = c;
//把较小的孩子作为下一个父亲,继续执行下沉操作
k = child;
}
//把key(其实就是x)放在他该在的位置
queue[k] = key;
}

4. 参考

12.Java SDK源码分析系列笔记-PriorityQueue的更多相关文章

  1. Spring Ioc源码分析系列--Ioc容器BeanFactoryPostProcessor后置处理器分析

    Spring Ioc源码分析系列--Ioc容器BeanFactoryPostProcessor后置处理器分析 前言 上一篇文章Spring Ioc源码分析系列--Ioc源码入口分析已经介绍到Ioc容器 ...

  2. Spring Ioc源码分析系列--容器实例化Bean的四种方法

    Spring Ioc源码分析系列--实例化Bean的几种方法 前言 前面的文章Spring Ioc源码分析系列--Bean实例化过程(二)在讲解到bean真正通过那些方式实例化出来的时候,并没有继续分 ...

  3. Spring mvc源码分析系列--Servlet的前世今生

    Spring mvc源码分析系列--Servlet的前世今生 概述 上一篇文章Spring mvc源码分析系列--前言挖了坑,但是由于最近需求繁忙,一直没有时间填坑.今天暂且来填一个小坑,这篇文章我们 ...

  4. jQuery源码分析系列

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...

  5. MyCat源码分析系列之——结果合并

    更多MyCat源码分析,请戳MyCat源码分析系列 结果合并 在SQL下发流程和前后端验证流程中介绍过,通过用户验证的后端连接绑定的NIOHandler是MySQLConnectionHandler实 ...

  6. MyCat源码分析系列之——BufferPool与缓存机制

    更多MyCat源码分析,请戳MyCat源码分析系列 BufferPool MyCat的缓冲区采用的是java.nio.ByteBuffer,由BufferPool类统一管理,相关的设置在SystemC ...

  7. MyCat源码分析系列之——前后端验证

    更多MyCat源码分析,请戳MyCat源码分析系列 MyCat前端验证 MyCat的前端验证指的是应用连接MyCat时进行的用户验证过程,如使用MySQL客户端时,$ mysql -uroot -pr ...

  8. jquery2源码分析系列

    学习jquery的源码对于提高前端的能力很有帮助,下面的系列是我在网上看到的对jquery2的源码的分析.等有时间了好好研究下.我们知道jquery2开始就不支持IE6-8了,从jquery2的源码中 ...

  9. [Tomcat 源码分析系列] (二) : Tomcat 启动脚本-catalina.bat

    概述 Tomcat 的三个最重要的启动脚本: startup.bat catalina.bat setclasspath.bat 上一篇咱们分析了 startup.bat 脚本 这一篇咱们来分析 ca ...

  10. [转]jQuery源码分析系列

    文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...

随机推荐

  1. Windows Terminal 添加 git-bash

    配置文件中 profiles 节点补充配置 { "guid": "{b453ae62-4e3d-5e58-b989-0a998ec441b7}", " ...

  2. Spring AI 增加混元 embedding 向量功能

    上次我们讨论了如何将自己的开源项目发布到 Maven 中央仓库,确保其能够方便地被其他开发者使用和集成.而我们的项目 spring-ai-hunyuan 已经具备了正常的聊天对话功能,包括文本聊天和图 ...

  3. Codeforces Round 970 (Div. 3)

    A. Sakurako's Exam 分类讨论即可,当a为奇数,无法消去1,或者a==0且b为奇数时,无法消去2 #include <bits/stdc++.h> using namesp ...

  4. 使用Python可视化磁场

    引言 随着科学技术的发展,物理学中的很多概念变得越来越复杂,但我们可以利用 Python 这一强大的工具,将一些抽象的物理现象变得更加直观易懂.今天,我们将以"磁场可视化"为主题, ...

  5. Mybatis-Plus中的@TableName 和 table-prefix

    简介 本文介绍Mybatis-Plus中的@TableName 和 table-prefix的使用. 介绍 在 MyBatis-Plus 中,@TableName 注解和 table-prefix 配 ...

  6. @Accessors lombok注解用法

    最近学习代码看到很多有趣的注解:慢慢整理下: @Accessors注解 @Accessors注解官方给出的解释是:面向getter和setter的更流畅的API.用于生成和查找getter和sette ...

  7. 智能简历解析器实战教程:基于Spacy+Flask构建自动化人才筛选系统

    一.项目背景与技术选型 在人力资源领域,每天需要处理数百份简历的HR团队面临巨大挑战:人工筛选效率低下.关键信息遗漏风险高.跨文档对比分析困难.本教程将构建一个端到端的智能简历解析系统,通过NLP技术 ...

  8. 为什么 G1 垃圾收集器不维护年轻代到老年代的记忆集?

    为什么 G1 垃圾收集器不维护年轻代到老年代的记忆集? 在 G1 垃圾收集器中,不维护年轻代到老年代的记忆集(Remembered Set, RSet)是因为其设计特点和优化策略使得这种记忆集的维护既 ...

  9. 强化学习框架:OpenRLHF源码解读,模型处理

    强化学习框架:OpenRLHF源码解读,模型处理 本文主要介绍 强化学习框架:OpenRLHF源码解读,模型处理 models框架设计 了解一下 OpenRLHF的模型框架设计范式: From:htt ...

  10. 在Podman中配置Dify Sandbox服务与外部PostgreSQL服务的网络连接

    在Podman中配置Dify Sandbox服务与外部PostgreSQL服务的网络连接 引言 在容器化环境中,确保不同服务之间的可靠通信是至关重要的.本文将指导你如何使用Podman来配置Dify ...