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. ANSYS 导出节点的位移数据

    1. 数据保存 确定待提取的节点编号: 获取节点位移变量: 将节点位移变量存储到数组中,用于数据传递: ! 输出对应节点的位移到csv文件 ! 注意同时导入.db和.rst,并切换到/post26模块 ...

  2. 学习Kotlin语法(四)

    简介 在上一节,我们对Kotlin中函数的相关知识有了大致的了解,本章节我们将去了解一些Kotlin中的作用域函数. 目录 let:处理可空对象,链式操作 run:对象配置 + 计算返回值 with: ...

  3. IDEA target中没有class文件/target中有class没有yml文件/yml文件不显示叶子

    target中没有class文件.表现为文件显示红波浪线,但是点进去自己又好了,但是编译会说找不到.点进入target文件夹发现没有class文件,只有yml文件或者什么都没有 解决方法:rebuil ...

  4. kette介绍-Step之Table input

    表输入(Table Input)介绍: Table input用于将数据源的数据加载到Kettle转换中的行集,可以说是数 据从持久化到内存的一种加载变换,故名为输入.加载内部过程,就是通过JDBC ...

  5. 基于Surprise和Flask构建个性化电影推荐系统:从算法到全栈实现

    一.引言:推荐系统的魔法与现实意义 在Netflix每年节省10亿美元内容采购成本的背后,在YouTube占据用户80%观看时长的推荐算法中,推荐系统正悄然改变内容消费模式.本文将带您从零开始构建一个 ...

  6. 在 Go 中,如何实现一个带过期时间的字典映射

    有些时候,应用系统用不上 redis,我们也可以用锁和 goroutine 实现一个带有过期时间的线程安全的字典. 这种字典的应用场景,比较倾向于数据规模较小,没有分布式要求. 下面是实现: 1.定义 ...

  7. 基于Cherry Studio + DeepSeek 搭建本地私有知识库!

    在当今数字化时代,知识管理变得越来越重要.无论是个人还是企业,都希望能够高效地存储.管理和检索知识.而借助 AI 技术,我们可以实现更加智能的知识库系统.本文将详细介绍如何使用 Cherry Stud ...

  8. ragflow k8s部署详细过程

    一.概述 ragflow官方提供的安装方式是docker-compose方式部署的,单机运行. k8s部署方式,暂未提供. 不过我们可以通过工具,结合docker-compose.yaml,来推演出对 ...

  9. chrome “从 Google 获取图片说明”

    右键菜单"从 Google 获取图片说明"多余去掉. 设置-高级-使用硬件加速模式(如果可用)-关闭 在用户使用上firefox完胜chrome,但是firefox的开发人员工具相 ...

  10. SpringBoot事件驱动开发

    应用启动过程生命周期事件感知(9大事件).应用运行中事件感知(无数种) 事件发布:ApplicationEventPublisherAware或注入:ApplicationEventMulticast ...