java优先队列类,【Java源码】集合类-优先队列PriorityQueue

favicon.ico摘要:

s!=0)//如果不止一个元素,调整结构siftDown(0,x);//返回队头元素returnresult;}删除元素,也得调整结构以维持优先队列内部数据结构为:堆五、简单用法下面是一段简单的事列代码,演示了自然排序和自定义排序的情况:packagecom.good.good.study.queue;importorg.slf4j.Logger;importorg.slf4j.LoggerFac

一、类继承关系

public class PriorityQueue extends AbstractQueue

implements java.io.Serializable {

PriorityQueue只实现了AbstractQueue抽象类也就是实现了Queue接口。

out(()=>{console.log("timeout");},0);setImmediate(()=>{console.log("immediate");})})$nodetimeo

二、类属性

//默认初始化容量

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; 的英文注释:

out(()=>{console.log("timeout");},0);setImmediate(()=>{console.log("immediate");})})$nodetimeo

Priority queue represented as a balanced binary heap: the two children of queue[n] are queue[2*n+1] and queue[2*(n+1)].

out(()=>{console.log("timeout");},0);setImmediate(()=>{console.log("immediate");})})$nodetimeo

我们可以知道PriorityQueue内部是通过完全二叉树(complete binary tree)实现的小顶堆(注意:这里我们定义的比较器为越小优先级越高)实现的。

out(()=>{console.log("timeout");},0);setImmediate(()=>{console.log("immediate");})})$nodetimeo

三、数据结构

优先队列PriorityQueue内部是通过堆实现的。堆分为大顶堆和小顶堆:

0ced119d-d7e4-4991-aace-91b9992744a9.jpg

out(()=>{console.log("timeout");},0);setImmediate(()=>{console.log("immediate");})})$nodetimeo

大顶堆:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;

小顶堆:每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。

堆通过数组来实现。

PriorityQueue内部是一颗完全二叉树实现的小顶堆。父子节点下表有如下关系:

leftNo = parentNo*2+1

rightNo = parentNo*2+2

parentNo = (nodeNo-1)/2

通过上面的公式可以很轻松的根据某个节点计算出父节点和左右孩子节点。

out(()=>{console.log("timeout");},0);setImmediate(()=>{console.log("immediate");})})$nodetimeo

四、常用方法

add()/offer()

其实add()方法内部也是调用了offer().下面是offer()的源码:

out(()=>{console.log("timeout");},0);setImmediate(()=>{console.log("immediate");})})$nodetimeo

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;

}

我们可以知道:

out(()=>{console.log("timeout");},0);setImmediate(()=>{console.log("immediate");})})$nodetimeo

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;

}

上面的源码中可知:

out(()=>{console.log("timeout");},0);setImmediate(()=>{console.log("immediate");})})$nodetimeo

利用堆的特点:parentNo = (nodeNo-1)/2 计算出父节点的下标,由此得到父节点:queue[parent];

如果插入的元素x大于父节点e那么循环退出,不做结构调整,x就插入在队列尾:queue[k] = x;

否则 queue[k] = e; k = parent; 父节点和加入的位置元素互换,如此循环,以维持最小堆。

下面是画的是向一个优先队列中添加(offer)一个元素过程的草图,以便理解:

d8602cbc-8c71-440c-ae1a-77d7634f7519.jpg

out(()=>{console.log("timeout");},0);setImmediate(()=>{console.log("immediate");})})$nodetimeo

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;

}

删除元素,也得调整结构以维持优先队列内部数据结构为:堆

out(()=>{console.log("timeout");},0);setImmediate(()=>{console.log("immediate");})})$nodetimeo

五、简单用法

下面是一段简单的事列代码,演示了自然排序和自定义排序的情况:

out(()=>{console.log("timeout");},0);setImmediate(()=>{console.log("immediate");})})$nodetimeo

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 priorityQueue = new PriorityQueue<>();

Random random = new Random();

int size = 10;

for (int i =0;i

priorityQueue.add(random.nextInt(100));

}

for (int i =0;i

logger.info("第 {} 次取出元素:{}",i,priorityQueue.poll());

}

}

/**

* 自定义排序规则,根据人的年龄排序

*/

private static void personOrdering(){

PriorityQueue priorityQueue = new PriorityQueue<>(new Comparator() {

@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

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来处理此种问题,以及需要考虑队列数据持久化。

out(()=>{console.log("timeout");},0);setImmediate(()=>{console.log("immediate");})})$nodetimeo

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值