【Java源码】集合类-优先队列PriorityQueue
一、类继承关系
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable {
PriorityQueue只实现了AbstractQueue抽象类也就是实现了Queue接口。
二、类属性
//默认初始化容量
private static final int DEFAULT_INITIAL_CAPACITY = 11;
//通过完全二叉树(complete binary tree)实现的小顶堆
transient Object[] queue;
private int size = 0;
//比较器
private final Comparator<? super E> comparator;
//队列结构被改变的次数
transient int modCount = 0;
根据transient Object[] queue; 的英文注释:
Priority queue represented as a balanced binary heap: the two children of queue[n] are queue[2n+1] and queue[2(n+1)].
我们可以知道PriorityQueue内部是通过完全二叉树(complete binary tree)实现的小顶堆(注意:这里我们定义的比较器为越小优先级越高)实现的。
三、数据结构
优先队列PriorityQueue内部是通过堆实现的。堆分为大顶堆和小顶堆:

- 大顶堆:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;
- 小顶堆:每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
- 堆通过数组来实现。
- PriorityQueue内部是一颗完全二叉树实现的小顶堆。父子节点下表有如下关系:
leftNo = parentNo*2+1
rightNo = parentNo*2+2
parentNo = (nodeNo-1)/2
通过上面的公式可以很轻松的根据某个节点计算出父节点和左右孩子节点。
四、常用方法
add()/offer()
其实add()方法内部也是调用了offer().下面是offer()的源码:
public boolean offer(E e) {
//不允许空
if (e == null)
throw new NullPointerException();
//modCount记录队列的结构变化次数。
modCount++;
int i = size;
//判断
if (i >= queue.length)
//扩容
grow(i + 1);
//size加1
size = i + 1;
if (i == 0)
queue[0] = e;
else
//不是第一次添加,调整树结构
siftUp(i, e);
return true;
}
我们可以知道:
- add()和offer()是不允许空值的插入。
- 和List一样,有fail-fast机制,会有modCount来记录队列的结构变化,在迭代和删除的时候校验,不通过会报ConcurrentModificationException。
- 判断当前元素size大于等于queue数组的长度,进行扩容。如果queue的大小小于64扩容为原来的两倍再加2,反之扩容为原来的1.5倍。
扩容函数源码如下:
private void grow(int minCapacity) {
int oldCapacity = queue.length;
// Double size if small; else grow by 50%
//如果queue的大小小于64扩容为原来的两倍再加2,反之扩容为原来的1.5倍
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
//右移一位
(oldCapacity >> 1));
// overflow-conscious code
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
queue = Arrays.copyOf(queue, newCapacity);
}
- 加入第一个元素时,queue[0] = e;以后加入元素内部数据结构会进行二叉树调整,维持最小堆的特性:调用siftUp(i, e):
private void siftUp(int k, E x) {
//比较器非空情况
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
//比较器非空情况
@SuppressWarnings("unchecked")
private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
//利用堆的特性:parentNo = (nodeNo-1)/2
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;
}
上面的源码中可知:
- 利用堆的特点:parentNo = (nodeNo-1)/2 计算出父节点的下标,由此得到父节点:queue[parent];
- 如果插入的元素x大于父节点e那么循环退出,不做结构调整,x就插入在队列尾:queue[k] = x;
- 否则 queue[k] = e; k = parent; 父节点和加入的位置元素互换,如此循环,以维持最小堆。
下面是画的是向一个优先队列中添加(offer)一个元素过程的草图,以便理解:

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;
}
删除元素,也得调整结构以维持优先队列内部数据结构为:堆
五、简单用法
下面是一段简单的事列代码,演示了自然排序和自定义排序的情况:
package com.good.good.study.queue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Random;
/**
* @author monkjavaer
* @version V1.0
* @date 2019/6/16 0016 10:22
*/
public class PriorityQueueTest {
/**日志记录*/
private static final Logger logger = LoggerFactory.getLogger(PriorityQueueTest.class);
public static void main(String[] args) {
naturalOrdering();
personOrdering();
}
/**
* 自然排序
*/
private static void naturalOrdering(){
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
Random random = new Random();
int size = 10;
for (int i =0;i<size;i++){
priorityQueue.add(random.nextInt(100));
}
for (int i =0;i<size;i++){
logger.info("第 {} 次取出元素:{}",i,priorityQueue.poll());
}
}
/**
* 自定义排序规则,根据人的年龄排序
*/
private static void personOrdering(){
PriorityQueue<Person> priorityQueue = new PriorityQueue<>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge()-o2.getAge();
}
});
Random random = new Random();
int size = 10;
for (int i =0;i<size;i++){
priorityQueue.add(new Person(random.nextInt(100)));
}
while (true){
Person person = priorityQueue.poll();
if (person == null){
break;
}
logger.info("取出Person:{}",person.getAge());
}
}
}
六、应用场景
优先队列PriorityQueue常用于调度系统优先处理VIP用户的请求。多线程环境下我们需要使用PriorityBlockingQueue来处理此种问题,以及需要考虑队列数据持久化。
【Java源码】集合类-优先队列PriorityQueue的更多相关文章
- 【java集合框架源码剖析系列】java源码剖析之TreeSet
本博客将从源码的角度带领大家学习TreeSet相关的知识. 一TreeSet类的定义: public class TreeSet<E> extends AbstractSet<E&g ...
- 【java集合框架源码剖析系列】java源码剖析之HashSet
注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本.本博客将从源码角度带领大家学习关于HashSet的知识. 一HashSet的定义: public class HashSet&l ...
- 【笔记0-开篇】面试官系统精讲Java源码及大厂真题
背景 开始阅读 Java 源码的契机,还是在第一年换工作的时候,被大厂的技术面虐的体无完肤,后来总结大厂的面试套路,发现很喜欢问 Java 底层实现,即 Java 源码,于是我花了半年时间,啃下了 J ...
- Java源码赏析(三)初识 String 类
由于String类比较复杂,现在采用多篇幅来讲述 这一期主要从String使用的关键字,实现的接口,属性以及覆盖的方法入手.省略了大部分的字符串操作,比如split().trim().replace( ...
- 如何阅读Java源码 阅读java的真实体会
刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比 ...
- Android反编译(一)之反编译JAVA源码
Android反编译(一) 之反编译JAVA源码 [目录] 1.工具 2.反编译步骤 3.实例 4.装X技巧 1.工具 1).dex反编译JAR工具 dex2jar http://code.go ...
- 如何阅读Java源码
刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动.源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比方吧, ...
- Java 源码学习线路————_先JDK工具包集合_再core包,也就是String、StringBuffer等_Java IO类库
http://www.iteye.com/topic/1113732 原则网址 Java源码初接触 如果你进行过一年左右的开发,喜欢用eclipse的debug功能.好了,你现在就有阅读源码的技术基础 ...
- Programming a Spider in Java 源码帖
Programming a Spider in Java 源码帖 Listing 1: Finding the bad links (CheckLinks.java) import java.awt. ...
- 解密随机数生成器(二)——从java源码看线性同余算法
Random Java中的Random类生成的是伪随机数,使用的是48-bit的种子,然后调用一个linear congruential formula线性同余方程(Donald Knuth的编程艺术 ...
随机推荐
- cacti支持中文办法
1.yum groupinstall "chinese support" 2. 登陆Cacti,在主页的左边点击setting,选择paths页(console>>se ...
- Idea导入tomcat源码
1.下载资源 下载主要包含两个包,已经编译的包和源码包,如图所示. 链接地址为: http://mirror.bit.edu.cn/apache/tomcat/tomcat-7/v7.0.93/bin ...
- Ubuntu16.04常用操作命令总结ing
查看软件安装目录:whereis 软件名称(如:whereis mysql,where is sqlite3等) 安装软件:apt/apt-get install 软件名称(如:apt/apt-get ...
- Chrome开发者工具关于网络请求的一个隐藏技能
这个隐藏技能的背景是,最近出于学习目的,我写了一个百度贴吧的网络爬虫,专门爬取一些指定主题的贴吧帖子. 抓取帖子用的JavaScript函数如下: function getPostByAJAX(req ...
- (转)Spring4.2.5+Hibernate4.3.11+Struts1.3.8集成方案一
http://blog.csdn.net/yerenyuan_pku/article/details/52888808 前面我们已经集成了Spring4.2.5+Hibernate4.3.11这两个框 ...
- Django 路由 —— Djangon如何处理一个请求
Django URL路由概述 一个干净优雅的URL方案是高质量Web应用程序中的一个重要细则Django可以让你设计URL,无论你想要什么,没有框剪限制要为应用程序设计URL,您可以非正式地创建一个名 ...
- iview table icon dorpdown html页面级别vue组件 #vuez#
iview table icon dorpdown html页面级别vue组件 <!DOCTYPE html> <html> <head> <meta cha ...
- JavaSE-25 AWT
学习要点 关于AWT AWT容器 布局管理器 AWT组件 事件处理 关于AWT java.awt包与子包 AWT软件包 说明 import java.awt.*; 基本组件使用工具 import ...
- 框模型中设置内容区域元素占地尺寸box-sizing属性
盒子模型有两种 一种是 内容盒子模型(content-box)一种是边框盒子模型(border-box). content-box:设置的尺寸,只设置内容区域, 左外边距+左边框+左内边距+内容区域宽 ...
- 遇到的django问题
问题1: No migrations to apply 删除了migrations中0001_initial.py文件,重新执行 python manage.py makemigrations pyt ...