PriorityQueue和Queue的一种变体的实现
队列和优先队列是我们十分熟悉的数据结构。提供了所谓的“先进先出”功能,优先队列则按照某种规则“先进先出”。但是他们都没有提供:“固定大小的队列”和“固定大小的优先队列”的功能。
比如我们要实现:记录按照时间排序的最近的登录网站的20个人;按照分数排序的最高的30个人;比如在游戏中一场两两PK的战斗,得分最高的6个人;要实现这些功能时,需要的数据结构,在java类库中没有现成的类。我们需要利用现有的类库来实现它们。
1. 固定大小的“先进先出”队列
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue; public class TopQueue<E> {
private final LinkedBlockingQueue<E> blockQueue; public TopQueue(int size){
this.blockQueue = new LinkedBlockingQueue<E>(size);
} public synchronized void put(E e) throws InterruptedException{
if(blockQueue.offer(e)){
return;
}else{
blockQueue.take();
blockQueue.offer(e);
}
} public List<E> getAll(){
return new ArrayList<E>(blockQueue);
} public static void main(String[] args) throws InterruptedException{
TopQueue<Integer> tq = new TopQueue<Integer>(3);
tq.put(1);
tq.put(2);
tq.put(3);
System.out.println(Arrays.toString(tq.getAll().toArray())); tq.put(4);
System.out.println(Arrays.toString(tq.getAll().toArray())); tq.put(5);
System.out.println(Arrays.toString(tq.getAll().toArray())); tq.put(6);
System.out.println(Arrays.toString(tq.getAll().toArray()));
}
}
输出的结果为:
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
[4, 5, 6]
上面的TopQueue实现了“固定大小的线程安全的”队列。无论有多少个线程,向TopQueue中放入了多少个元素,在TopQueue中只保留最后放进去的n个元素。
2. 固定大小的优先队列(实现一)
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.PriorityBlockingQueue;
import com.alibaba.fastjson.JSON; public class TopPriorityQueue<E> {
private final PriorityBlockingQueue<E> blockQueue;
private final int size; public TopPriorityQueue(int size){
this.blockQueue = new PriorityBlockingQueue<E>(size + 1);
this.size = size + 1; // 这里多加1的原因是防止put方法中将大的删除了,反而降小的插入了,所以多加1个用做"哨卡"
} public synchronized void put(E e) throws InterruptedException{
if(blockQueue.size() >= size)
blockQueue.take();
blockQueue.offer(e);
} public List<E> getAll() throws InterruptedException{
synchronized(this){
if(blockQueue.size() >= size)
blockQueue.take(); // 前面构造函数中多加了1,这里减掉一个
}
return new ArrayList<E>(blockQueue);
} public static void main(String[] args) throws InterruptedException{
final TopPriorityQueue<User> tq = new TopPriorityQueue<User>(3);
User u1 = new User(1, "bbb", 10);
User u2 = new User(2, "ccc", 20);
User u3 = new User(3, "ddd", 30);
User u4 = new User(4, "fff", 40);
User u5 = new User(5, "fff", 50);
User u6 = new User(6, "ddd", 60);
User u7 = new User(7, "ggg", 70);
User u8 = new User(8, "hhh", 80); tq.put(u4); //
System.out.println(JSON.toJSONString(tq.getAll()));
tq.put(u8); //4,8
System.out.println(JSON.toJSONString(tq.getAll()));
tq.put(u7); //4,8,7
System.out.println(JSON.toJSONString(tq.getAll()));
tq.put(u5); //5,8,7
System.out.println(JSON.toJSONString(tq.getAll()));
tq.put(u2); //5,8,7
System.out.println(JSON.toJSONString(tq.getAll()));
tq.put(u3); //5,8,7
System.out.println(JSON.toJSONString(tq.getAll()));
tq.put(u1); //5,8,7
System.out.println(JSON.toJSONString(tq.getAll()));
tq.put(u6); //6,8,7
System.out.println(JSON.toJSONString(tq.getAll()));
}
}
User类:
import java.util.Comparator;
public class User implements Comparable<User>{
private int id;
private String name;
private long score; // 得分
// ... ...
public User(int id, String name, long score){
this.id = id;
this.name = name;
this.score = score;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getScore() {
return score;
}
public void setScore(long score) {
this.score = score;
}
@Override
public int compareTo(User o) {
return this.getScore() > o.getScore() ? 1 : this.getScore() < o.getScore() ? -1 : 0;
}
}
输入的结果为:
[{"id":4,"name":"fff","score":40}]
[{"id":4,"name":"fff","score":40},{"id":8,"name":"hhh","score":80}]
[{"id":4,"name":"fff","score":40},{"id":8,"name":"hhh","score":80},{"id":7,"name":"ggg","score":70}]
[{"id":5,"name":"fff","score":50},{"id":8,"name":"hhh","score":80},{"id":7,"name":"ggg","score":70}]
[{"id":5,"name":"fff","score":50},{"id":8,"name":"hhh","score":80},{"id":7,"name":"ggg","score":70}]
[{"id":5,"name":"fff","score":50},{"id":8,"name":"hhh","score":80},{"id":7,"name":"ggg","score":70}]
[{"id":5,"name":"fff","score":50},{"id":8,"name":"hhh","score":80},{"id":7,"name":"ggg","score":70}]
[{"id":6,"name":"ddd","score":60},{"id":8,"name":"hhh","score":80},{"id":7,"name":"ggg","score":70}]
TopPriorityQueue实现了“固定大小的优先队列”,的实现原理是:
public synchronized void put(E e) throws InterruptedException{
if(blockQueue.size() >= size)
blockQueue.take();
blockQueue.offer(e);
}
当队列满了,还要插入时,就删除队列中最小的一个,然后再插入。但是这里涉及到一个问题,如果这个要被插入的元素优先级要比那个被删除的元素优先级低呢?那岂不是将大的删除了,反而将小的插入了。所以这里我们采取的办法是,比实际要求的size的基础上多保留一个,用做“哨卡”。当队列满了时,我们将“哨卡”删掉,然后再插入我们的元素,然后队列中新的最小的元素就成为了新的“哨卡”。而“哨卡”因为是最小的一个,不是我们需要的,返回最终结果时会被删除掉。所以不会出现删除了大的,插入了小的问题。这里有点小技巧。
3. 固定大小的优先队列(实现二)
上面的实现,需要我们插入队列的元素Comparable这个接口,但是实际环境中,我们不太可能去进行这样的修改,所以我们还有另外一种方法——使用Comparator来搞定,看代码:
import java.util.Comparator;
import com.coin.User; public class MyComparator implements Comparator<User> {
@Override
public int compare(User u1, User u2) {
if(u1.getScore() > u2.getScore())
return 1;
if(u1.getScore() < u2.getScore())
return -1;
return 0;
}
}
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.PriorityBlockingQueue; import com.alibaba.fastjson.JSON; public class TopPriorityQueue<E> {
private final PriorityBlockingQueue<E> blockQueue;
private final int size; public TopPriorityQueue(int size, Comparator<E> comparator){
this.blockQueue = new PriorityBlockingQueue<E>(size + 1, comparator);
this.size = size + 1; // 这里多加1的原因是防止put方法中将大的删除了,反而降小的插入了,所以多加1个用做"哨卡"
} public synchronized void put(E e) throws InterruptedException{
if(blockQueue.size() >= size)
blockQueue.take();
blockQueue.offer(e);
} public List<E> getAll() throws InterruptedException{
synchronized(this){
if(blockQueue.size() >= size)
blockQueue.take(); // 前面构造函数中多加了1,这里减掉一个
} return new ArrayList<E>(blockQueue);
} public static void main(String[] args) throws InterruptedException{
MyComparator myComparator = new MyComparator();
final TopPriorityQueue<User> tq = new TopPriorityQueue<User>(3, myComparator);
User u1 = new User(1, "bbb", 10);
User u2 = new User(2, "ccc", 20);
User u3 = new User(3, "ddd", 30);
User u4 = new User(4, "fff", 40);
User u5 = new User(5, "fff", 50);
User u6 = new User(6, "ddd", 60);
User u7 = new User(7, "ggg", 70);
User u8 = new User(8, "hhh", 80); tq.put(u4); //
System.out.println(JSON.toJSONString(tq.getAll()));
tq.put(u8); //4,8
System.out.println(JSON.toJSONString(tq.getAll()));
tq.put(u7); //4,8,7
System.out.println(JSON.toJSONString(tq.getAll()));
tq.put(u5); //5,8,7
System.out.println(JSON.toJSONString(tq.getAll()));
tq.put(u2); //5,8,7
System.out.println(JSON.toJSONString(tq.getAll()));
tq.put(u3); //5,8,7
System.out.println(JSON.toJSONString(tq.getAll()));
tq.put(u1); //5,8,7
System.out.println(JSON.toJSONString(tq.getAll()));
tq.put(u6); //6,8,7
System.out.println(JSON.toJSONString(tq.getAll()));
}
}
所以我们在使用PriorityBlockingQueue时,要么我们插入的元素实现了Comparable这个接口,要么我定义一个Comparator,传入到PriorityBlockingQueue的构造函数中,我们可以看下PriorityBlockingQueue.offer(e)方法的源码,它会对这两种情况进行判断:
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock();
int n, cap;
Object[] array;
while ((n = size) >= (cap = (array = queue).length))
tryGrow(array, cap);
try {
Comparator<? super E> cmp = comparator;
if (cmp == null)
siftUpComparable(n, e, array);
else
siftUpUsingComparator(n, e, array, cmp);
size = n + 1;
notEmpty.signal();
} finally {
lock.unlock();
}
return true;
}
其中的代码:
Comparator<? super E> cmp = comparator;
if (cmp == null)
siftUpComparable(n, e, array);
else
siftUpUsingComparator(n, e, array, cmp);
就是判断我们是否在PriorityBlockingQueue的构造函数中是否传入了Comparator。这样User类就不需要实现Comparable接口了。
另外我们要注意 LinkedBlockingQueue 和 PriorityBlockingQueue 有一点不同,BlockingQueue.offer(e)在队列满了时,会返回false,而PriorityBlockingQueue.offer()即使队列满了,它会进行扩展,永远只返回true.
LinkedBlockingQueue .offer() 的源码如下:
/**
* Inserts the specified element at the tail of this queue if it is
* possible to do so immediately without exceeding the queue's capacity,
* returning {@code true} upon success and {@code false} if this queue
* is full.
* When using a capacity-restricted queue, this method is generally
* preferable to method {@link BlockingQueue#add add}, which can fail to
* insert an element only by throwing an exception.
*
* @throws NullPointerException if the specified element is null
*/
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
final AtomicInteger count = this.count;
if (count.get() == capacity)
return false;
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
if (count.get() < capacity) {
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
}
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
return c >= 0;
}
当满了时返回false:
if (count.get() == capacity)
return false;
PriorityBlockingQueue.offer() 的源码如下:
/**
* Inserts the specified element into this priority queue.
* As the queue is unbounded, this method will never return {@code false}.
*
* @param e the element to add
* @return {@code true} (as specified by {@link Queue#offer})
* @throws ClassCastException if the specified element cannot be compared
* with elements currently in the priority queue according to the
* priority queue's ordering
* @throws NullPointerException if the specified element is null
*/
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock();
int n, cap;
Object[] array;
while ((n = size) >= (cap = (array = queue).length))
tryGrow(array, cap);
try {
Comparator<? super E> cmp = comparator;
if (cmp == null)
siftUpComparable(n, e, array);
else
siftUpUsingComparator(n, e, array, cmp);
size = n + 1;
notEmpty.signal();
} finally {
lock.unlock();
}
return true;
}
当满了时,会扩容:
while ((n = size) >= (cap = (array = queue).length))
tryGrow(array, cap);
As the queue is unbounded, this method will never return {@code false}.
另外TopQueue 和 TopPriorityQueue 都是线程安全的,但是并不保证插入队列中的元素自身的线程安全性。
PriorityQueue和Queue的一种变体的实现的更多相关文章
- (转) 干货 | 图解LSTM神经网络架构及其11种变体(附论文)
干货 | 图解LSTM神经网络架构及其11种变体(附论文) 2016-10-02 机器之心 选自FastML 作者:Zygmunt Z. 机器之心编译 参与:老红.李亚洲 就像雨季后非洲大草原许多野 ...
- 二分查找及几种变体的Python实现
1. 在不重复的有序数组中,查找等于给定值的元素 循环法 def search(lst, target): n = len(lst) if n == 0: return -1 low = 0 high ...
- 二叉查找树及B-树、B+树、B*树变体
动态查找树主要有二叉查找树(Binary Search Tree),平衡二叉查找树(Balanced Binary Search Tree), 红黑树 (Red-Black Tree ), 都是典型的 ...
- .NET C#杂谈(1):变体 - 协变、逆变与不变
0. 文章目的: 介绍变体的概念,并介绍其对C#的意义 1. 阅读基础 了解C#进阶语言功能的使用(尤其是泛型.委托.接口) 2. 从示例入手,理解变体 变体这一概念用于描述存在继承关系的 ...
- 完全图解RNN、RNN变体、Seq2Seq、Attention机制
完全图解RNN.RNN变体.Seq2Seq.Attention机制 本文主要是利用图片的形式,详细地介绍了经典的RNN.RNN几个重要变体,以及Seq2Seq模型.Attention机制.希望这篇文章 ...
- 实现Promise的first等各种变体
本篇文章主要是想通过ES6中Promise提供的几个方法,来实现诸如first.last.none.any等各种变体方法! 在标准的ES6规范中,提供了Promise.all和Promise.race ...
- RNN-GRU-LSTM变体详解
首先介绍一下 encoder-decoder 框架 中文叫做编码-解码器,它一个最抽象的模式可以用下图来展现出来: 这个框架模式可以看做是RNN的一个变种:N vs M,叫做Encoder-Decod ...
- Gradle for Android ( 构建变体 )
链接: http://77blogs.com/?p=38 https://www.cnblogs.com/tangZH/p/10999060.html 有时候我们一个app需要有不同的版本,不同的版本 ...
- Prafab Varient 预制体变体
预制体与类的类比思维: 预制体相当于一个类,当它应用到场景当中,就是一个实例. 类的继承特性也充分运用到预制体中,即预制体变体. 相似预制体的需求场景: 例子1:多个游戏的窗口 ...
随机推荐
- 通过Gradle为APK瘦身
引言:在过去几年中,APK 文件的大小曾急剧增长态势.一般来说,其原因如下:Android开发者获取了更多的依赖库,添加了更多的密度,Apps 增加了更多的功能.但实际上我们应该让APKs 尽可能的小 ...
- Jmeter正则表达式
Jmeter正则表达式 文章转自:http://www.cnblogs.com/jamesping/articles/2252675.html 正则表达式可以帮助我们更好的描述复杂的文本格式.一旦你描 ...
- WCF学习之旅—实现支持REST客户端应用(二十四)
WCF学习之旅—实现REST服务(二十二) WCF学习之旅—实现支持REST服务端应用(二十三) 在上二篇文章中简单介绍了一下RestFul与WCF支持RestFul所提供的方法,及创建一个支持RES ...
- 阿里云自定义日记文件无法通过ftp下载
异常处理汇总 ~ 修正果带着你的Net飞奔吧!http://www.cnblogs.com/dunitian/p/4599258.html 有可能是个例,xftp不行(对linux支持很好),Cute ...
- 【Win 10应用开发】响应系统回退键的导航事件
按例,老周今天要讲一个故事,这个故事之前老周在微博上分享过.大伙知道在8.1的时候,有一个扩展类库——NotificationExtensions,可以真TMD轻松生成通知XML模板,其实,这个类库也 ...
- 简单编写Makefile
相信很多朋友都有过这样的经历,看着开源项目中好几页的makefile文件,不知所云.在日常学习和工作中,也有意无意的去回避makefile,能改就不写,能用ide就用ide.其实makefile并没有 ...
- JavaScript:让浏览器全屏显示
并不是所有人都会按F11让浏览器全屏显示~~~ 一.直接上代码 <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xh ...
- Asp.Net Core 项目实战之权限管理系统(0) 无中生有
0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...
- 翻译:使用 ASP.NET MVC 4, EF, Knockoutjs and Bootstrap 设计和开发站点 - 6 - 业务逻辑
Part 3: 设计逻辑层:核心开发 如前所述,我们的解决方案如下所示: 下面我们讨论整个应用的结构,根据应用中不同组件的逻辑相关性,分离到不同的层中,层与层之间的通讯通过或者不通过限制.分层属于架构 ...
- web设计页面跳转的方法
一.asp.net c# 打开新页面或页面跳转 1. 最常用的页面跳转(原窗口被替代):Response.Redirect("newpage.aspx"); 2. 利用url地址打 ...