一、类继承关系

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;
}

上面的源码中可知:

  1. 利用堆的特点:parentNo = (nodeNo-1)/2 计算出父节点的下标,由此得到父节点:queue[parent];
  2. 如果插入的元素x大于父节点e那么循环退出,不做结构调整,x就插入在队列尾:queue[k] = x;
  3. 否则 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的更多相关文章

  1. 【java集合框架源码剖析系列】java源码剖析之TreeSet

    本博客将从源码的角度带领大家学习TreeSet相关的知识. 一TreeSet类的定义: public class TreeSet<E> extends AbstractSet<E&g ...

  2. 【java集合框架源码剖析系列】java源码剖析之HashSet

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本.本博客将从源码角度带领大家学习关于HashSet的知识. 一HashSet的定义: public class HashSet&l ...

  3. 【笔记0-开篇】面试官系统精讲Java源码及大厂真题

    背景 开始阅读 Java 源码的契机,还是在第一年换工作的时候,被大厂的技术面虐的体无完肤,后来总结大厂的面试套路,发现很喜欢问 Java 底层实现,即 Java 源码,于是我花了半年时间,啃下了 J ...

  4. Java源码赏析(三)初识 String 类

    由于String类比较复杂,现在采用多篇幅来讲述 这一期主要从String使用的关键字,实现的接口,属性以及覆盖的方法入手.省略了大部分的字符串操作,比如split().trim().replace( ...

  5. 如何阅读Java源码 阅读java的真实体会

    刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心.   说到技术基础,我打个比 ...

  6. Android反编译(一)之反编译JAVA源码

    Android反编译(一) 之反编译JAVA源码 [目录] 1.工具 2.反编译步骤 3.实例 4.装X技巧 1.工具 1).dex反编译JAR工具  dex2jar   http://code.go ...

  7. 如何阅读Java源码

    刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动.源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比方吧, ...

  8. Java 源码学习线路————_先JDK工具包集合_再core包,也就是String、StringBuffer等_Java IO类库

    http://www.iteye.com/topic/1113732 原则网址 Java源码初接触 如果你进行过一年左右的开发,喜欢用eclipse的debug功能.好了,你现在就有阅读源码的技术基础 ...

  9. Programming a Spider in Java 源码帖

    Programming a Spider in Java 源码帖 Listing 1: Finding the bad links (CheckLinks.java) import java.awt. ...

  10. 解密随机数生成器(二)——从java源码看线性同余算法

    Random Java中的Random类生成的是伪随机数,使用的是48-bit的种子,然后调用一个linear congruential formula线性同余方程(Donald Knuth的编程艺术 ...

随机推荐

  1. UART协议

    通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,通常称作UART,读音/ˈjuːart/)是一种异步收发传输器,是电脑硬件的一部分,将资料由 ...

  2. EOS Dapp体验报告

    EOS Dapp体验报告 EOS通过并行链和DPOS的方式解决了延迟和数据吞吐量的难题. EOS能够实现每秒百万级的处理量,而目前比特币是每秒7笔,以太坊是30-40笔,EOS的这一超强能力吊打比特币 ...

  3. Hadoop 常用命令之 HDFS命令

    命令 说明 hadoop fs -mkdir 创建HDFS目录 hadoop fs -ls 列出HDFS目录 hadoop fs -copyFromLocal 使用-copyFromLocal 复制本 ...

  4. python * urllib_urlopen( )

    python * urllib_urlopen( ) Python urllib 库提供了一个从指定的 URL 地址获取网页数据,然后对其进行分析处理,获取想要的数据. 一.urllib模块urlop ...

  5. 聊天室(C++客户端+Pyhton服务器)_1.框架搭设

    聊天室 一.客户端发送 用MFC可视化做个客户端登录界面. 先点击注册账号按钮,注册账号的时候就需要连接到服务器, 服务器需要查数据库,并做出相应的回应. 所以开始写C++客户端套接口类用来连接到服务 ...

  6. 微信小程序---目录结构

    (1)目录结构 小程序包含一个描述整体程序的 app 和多个描述各自页面的 page.一个小程序主体部分由三个文件组成,必须放在项目的根目录,如下: (2)允许上传的文件 .

  7. There is no Action mapped for namespace [/] and action name [updateUser] associated with context path [].

    在使用Struts2的时候,遇到了这个问题. 原因分析: 找不到指定的路径, 那么就是struts.xml的内容问题, 或者是struts.xml的文件位置存在问题. struts2默认是应该放在sr ...

  8. JavaSE-31 Java正则表达式

    概述 正则表达式是一个强大的字符串处理工具,可以实现对字符串的查找.提取.分割.替换等操作. String类的几个方法需要依赖正则表达式的支持. 方法 方法说明 boolean matches(Str ...

  9. NETCORE使用DB First

    1)引用 (1)Install-Package Microsoft.EntityFrameworkCore (2)Install-Package Microsoft.EntityFrameworkCo ...

  10. MySQL InnoDB配置统计信息

    MySQL InnoDB配置统计信息 1. 配置持久化(Persistent)统计信息参数 1.1 配置自动触发更新统计信息参数 1.2 配置每张表的统计参数 1.3 配置InnoDB优化器统计信息的 ...