Queue-PriorityQueue源码解析
Queue队列通常是先进先出(FIFO),但也有特殊的非FIFO,如本文也分析的PriorityQueue。
Queue接口
Queue接口定义的方法:
添加元素接口:
- add(E e) -> 往队列添加一个元素,如果队列已满抛出IllegalStateException异常。
- offer(E e) -> 往队列添加一个元素,true成功,false失败,和add区别在与不会因为队列已满抛异常。
删除元素接口:
- remove() -> 删除队列头元素并返回该元素,如果队列为空抛出NoSuchElementException异常。
- E poll() -> 删除队列头元素并返回该元素,如果队列为空返回null(与remove不同)。
获取队列头元素接口:
- E element() -> 返回队列头部元素(没有删除),如果队列为空抛出NoSuchElementException异常。
- E peek() -> 返回队列头部元素(没有删除),如果队列为空返回null。
Queue常用的实现类
上图中列出的是Queue平时常用的实现类:
- ArrayBlockingQueue -> 有边界的数组形式实现的阻塞队列。
- LinkedBlockingQueue -> 有边界的链表形式实现的阻塞队列。
- PriorityQueue -> 无边界的二叉堆形式实现的优先级队列。
- DelayQueue -> 无边界的优先级形式实现的延迟队列。
PriorityQueue
PriorityQueue是基于二叉堆形式实现的无界队列。队列中元素类型必须是可比较的,构造函数如果没有传入Comparator默认是自然排序。
PriorityQueue结构
PriorityQueue继承了AbstractQueue,AbstractQueue实现Queue接口,即PriorityQueue拥有Queue的方法和特征。
Object[] queue:存放队列元素。
int DEFAULT_INITIAL_CAPACITY:默认的队列大小,默认值为11。
int size:PriorityQueue队列中元素个数。
int modCount:PriorityQueue队列修改次数。
Comparator<? super E> comparator:队列元素排序比较器。
int MAX_ARRAY_SIZE:队列最大值(Integer.MAX_VALUE - 8),VM的保留了8字节的 header words。
PriorityQueue示例
package com.juc.queue;
import java.util.PriorityQueue;
/**
* Created on 2020/5/10 23:29.
* @author Griez
*/
public class PriorityQueueTest {
public static final PriorityQueue<Integer> QUEUE = new PriorityQueue<>();
public static void main(String[] args) {
for (int i = 10; i > 0 ; i--) {
QUEUE.offer(i);
}
for (int i = 0; i < 10; i++) {
System.out.println(QUEUE.poll());
}
}
}
创建一个存放Integer的PriorityQueue,采用默认的自然排序。并倒序的往PriorityQueue添加10-1。然后从PriorityQueue头部出队列并输出,输出结果是1-10升序。如果是让我们实现应该是入队时用插叙排序好并存放在queue数组中,但是这样实现往queue数组中添加和删除元素移动次数是不是最优的呢?接下来我们看一下Josh Bloch, Doug Lea是怎么样实现的。
PriorityQueue添加元素解析
java.util.PriorityQueue#offer
public boolean offer(E e) {
if (e == null) //《1》不能为空
throw new NullPointerException();
modCount++; // 《2》修改次数加1
int i = size;
if (i >= queue.length) // 默认11
grow(i + 1); // 《3》数组扩容
size = i + 1;
if (i == 0) // 《4》直接把e赋值给0下标元素(顶部元素)
queue[0] = e;
else
siftUp(i, e); // 《5》筛选顶部元素
return true;
}
《1》添加的元素不能为空,即PriorityQueue队列不可能存在null元素。
《2》修改次数加1。
《3》如果当前PriorityQueue元素数量大于等于数组容量需要对queue进行扩容操作。
《4》如果当前PriorityQueue为空,直接把e赋值给queue数组0下标(顶部元素)。
《5》通过二叉堆,筛选顶部元素。
java.util.PriorityQueue#grow
private void grow(int minCapacity) {
int oldCapacity = queue.length;
// Double size if small; else grow by 50%
// 《1》根据现有的容量选择增长倍数
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
// overflow-conscious code
// 《2》如果《1》计算出的容量比最大大,则以传入容量为准
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
queue = Arrays.copyOf(queue, newCapacity);
}
《1》根据现有的容量选择增长倍数,如果现在的容量小于64,则容量直接增长一倍再加2;否则增长50%。
《2》如果《1》计算出的容量比最大大,则以传入容量为准。
java.util.PriorityQueue#siftUp
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
如果构造PriorityQueue时传有特定比较器,就按特定比较器方式设置顶部元素,否则按默认自然比较器方式设置。
java.util.PriorityQueue#siftUpComparable
private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x; //《1》
while (k > 0) {
int parent = (k - 1) >>> 1; //《2》
Object e = queue[parent]; //《3》
if (key.compareTo((E) e) >= 0) //《4》
break;
queue[k] = e; //《5》
k = parent;
}
queue[k] = key; //《6》
}
《1》添加的元素必须是Comparable子类,可比较的。
《2》计算父节点下标。
《3》得到父节点元素。
《4》跟父节点元素作比较,如果要添加的元素大于父节点元素则退出。
《5》把父节点的元素移动到数组下标k处,然后把父节点下标赋值给k,循环《1》 - 《4》步骤。
《6》经过前面步骤最终确认需要添加的元素在queue下标,并存入数组。
添加10 - 8 该方法体现的数据结构。
添加7整个过程,用堆数据结构添加7的过程只交换了两次数据位置。如果用插叙排序这种极端情况所有数据都需要移动。
最小二叉堆特性是根节点元素值永远是最小的。
PriorityQueue删除元素解析
java.util.PriorityQueue#poll
public E poll() {
if (size == 0) //《1》
return null;
int s = --size; //《2》
modCount++; //《3》
E result = (E) queue[0];//《4》
E x = (E) queue[s];//《5》
queue[s] = null;
if (s != 0)
siftDown(0, x);//《6》
return result;
}
《1》如果队列为空,返回null。
《2》队列元素总数减1。
《3》修改次数加1。
《4》把堆头部元素取出,后面直接返回该元素。
《5》获取queue最后一个元素并把该位置设置null。
《6》重新筛选最小值为头部元素。
java.util.PriorityQueue#siftDown
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
如果构造PriorityQueue时传有特定比较器,就按特定比较器方式设置顶部元素,否则按默认自然比较器方式设置。
java.util.PriorityQueue#siftDownComparable
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>)x;
int half = size >>> 1; //《1》 // loop while a non-leaf
while (k < half) {
int child = (k << 1) + 1; //《2》 // assume left child is least
Object c = queue[child];//《3》
int right = child + 1;//《4》
if (right < size &&
((Comparable<? super E>) c).compareTo((E) queue[right]) > 0) //《5》
c = queue[child = right];
if (key.compareTo((E) c) <= 0)//《6》
break;
queue[k] = c;//《7》
k = child;
}
queue[k] = key;//《8》
}
《1》无符号右移1位,取size的一半。
《2》得到二叉堆的左子节点下标。
《3》获取左子节点元素。
《4》右子节点下标。
《5》右子节点下标小于队列元素总数,并且左子节点元素比右子节点元素大时,把右子节点元素赋值给c,把右子节点下标赋值给child。
《6》需要交换的元素key小于或等于子节点元素c,则退出循环。
《7》把子节点c设置到queue下标为k的位置,并把child赋值给k,然后重复《1》-《6》步骤。
《8》找到key合适的位置并设置该元素。
总结
PriorityQueue使用二叉堆数据结构保证了队列头部元素永远是最小的,在添加和删除的过程元素移动次数比插叙排序插入少。队列元素是使用数组queue保存,在多线程的情况对数组queue并发操作存在安全问题。
Queue-PriorityQueue源码解析的更多相关文章
- 给jdk写注释系列之jdk1.6容器(12)-PriorityQueue源码解析
PriorityQueue是一种什么样的容器呢?看过前面的几个jdk容器分析的话,看到Queue这个单词你一定会,哦~这是一种队列.是的,PriorityQueue是一种队列,但是它又是一种什么样的队 ...
- 给jdk写注释系列之jdk1.6容器(11)-Queue之ArrayDeque源码解析
前面讲了Stack是一种先进后出的数据结构:栈,那么对应的Queue是一种先进先出(First In First Out)的数据结构:队列. 对比一下Stack,Queue是一种先进先出的容 ...
- 源码解析C#中PriorityQueue(优先级队列)的实现
前言 前段时间看到有大佬对.net 6.0新出的PriorityQueue(优先级队列)数据结构做了解析,但是没有源码分析,所以本着探究源码的心态,看了看并分享出来.它不像普通队列先进先出(FIFO) ...
- 源码解析Synchronous Queue 这种特立独行的队列
摘要:Synchronous Queue 是一种特立独行的队列,其本身是没有容量的,比如调用者放一个数据到队列中,调用者是不能够立马返回的,调用者必须等待别人把我放进去的数据消费掉了,才能够返回. 本 ...
- PriorityQueue源码分析
PriorityQueue其实是一个优先队列,和先进先出(FIFO)的队列的区别在于,优先队列每次出队的元素都是优先级最高的元素.那么怎么确定哪一个元素的优先级最高呢,jdk中使用堆这么一 ...
- Java并发包源码学习系列:阻塞队列实现之PriorityBlockingQueue源码解析
目录 PriorityBlockingQueue概述 类图结构及重要字段 什么是二叉堆 堆的基本操作 向上调整void up(int u) 向下调整void down(int u) 构造器 扩容方法t ...
- jQuery2.x源码解析(回调篇)
jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 通过艾伦的博客,我们能看出,jQuery的pro ...
- EventBus源码解析 源码阅读记录
EventBus源码阅读记录 repo地址: greenrobot/EventBus EventBus的构造 双重加锁的单例. static volatile EventBus defaultInst ...
- 源码解析-Volley(转自codeKK)
Volley 源码解析 本文为 Android 开源项目源码解析 中 Volley 部分项目地址:Volley,分析的版本:35ce778,Demo 地址:Volley Demo分析者:grumoon ...
- Android Handler机制(四)---Handler源码解析
Handler的主要用途有两个:(1).在将来的某个时刻执行消息或一个runnable,(2)把消息发送到消息队列. 主要依靠post(Runnable).postAtTime(Runnable, l ...
随机推荐
- redis和memcache列出所有key
//redis $redis = new Redis(); $redis->connect("host", "port"); $redis->sel ...
- 今天我们来谈谈绝对定位和相对定位的区别,和需要注意的问题;position:absolute|relative;
首先position:absolute|relative; 前者是绝对定位,后者是相对定位: position属性的四个值: static,relative,fixed,absolute; 重点重点重 ...
- python的历史和下载python解释器
一.python的诞生 1.Python的创始人为Guido van Rossum.1989年圣诞节期间,在阿姆斯特丹,Guido为了打发圣诞节的无趣,决心开发一个新的脚本解释程序,创造了一种C和sh ...
- python中文资源大全
Python 资源大全中文版 我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列的资源整理.awesome-python 是 vinta 发起维护的 Python 资源列 ...
- 0day学习笔记(2)--函数调用
函数调用过程 调用函数操作 函数参数入栈(在当前函数栈帧),从左至右或从右至左视情况而定 一般为从右至左 mov 地址,参数 的一个操作并不直接pop而是定位到地址将参数传递进去 call offse ...
- UDO、TCP、HTTP、websocket
如图:
- Android-网页解析-gson的使用
相对于较为传统的Json解析来说,google共享的开源Gson在解析速度和所使用的内存在有着明显的优势,虽然说阿里巴巴也提供了fastgson包,但是它跟Gson的处理速度大同小异,只是底层实现的原 ...
- DEDE Fatal error: Maximum execution time of 30 seconds exceeded 致命 错误: 最大的 执行 时间 为 30 秒
刚安的DEDE 5.7 -SP1-GBK的 为何一登录后台点任何链接都显示超过30秒 后台假死 网上搜的方法一般都是更改执行时间上限,其目的是为了解决一些大的数据,真的需要30秒以上的执行时 ...
- 第八次-非确定的自动机NFA确定化为DFA
提交作业 NFA 确定化为 DFA 子集法: f(q,a)={q1,q2,…,qn},状态集的子集 将{q1,q2,…,qn}看做一个状态A,去记录NFA读入输入符号之后可能达到的所有状态的集合. ...
- Debugging Under Unix: gdb Tutorial (https://www.cs.cmu.edu/~gilpin/tutorial/)
//注释掉 #include <iostream.h> //替换为 #include <iostream> using namespace std; Contents Intr ...