首先简单认识下Queue,查看API定义了6个方法,归纳为新增、删除、查询三种不同操作,每种操作有2个不同的实现,基于JDK 1.8
api接口如下:
新增接口
查询:
删除:
每种操作有不同的实现,他们区别是什么?实战操作
public class App
{
public static void main( String[] args ) {
//addAndOffer();
//removeAndPoll();
elementAndPeek();
}
/**
* element 和 peek 操作区别
*/
private static void elementAndPeek() {
// 设置一个固定长度为2的队列
Queue<String> queue = new ArrayBlockingQueue<String>(2);
try {
System.out.println(queue.element()); //java.util.NoSuchElementException
}catch (Exception e){
e.printStackTrace();
}
System.out.println(queue.poll()); //null
}
/**
* remove 和 poll 操作区别
*/
private static void removeAndPoll() {
// 设置一个固定长度为2的队列
Queue<String> queue = new ArrayBlockingQueue<String>(2);
try {
queue.add("a");
queue.add("b");
System.out.println(queue); // [a, b]
System.out.println(queue.remove()); // a
System.out.println(queue.remove()); // b
System.out.println(queue.remove()); // NoSuchElementException
}catch (Exception e){
e.printStackTrace();
}finally {
queue.clear();
}
queue.add("a");
queue.add("b");
System.out.println(queue);// [a, b]
System.out.println(queue.poll()); // a
System.out.println(queue.poll()); // b
System.out.println(queue.poll()); // null
}
/**
* add 和 offer 操作区别
*/
private static void addAndOffer() {
// 设置一个固定长度为2的队列
Queue<String> queue = new ArrayBlockingQueue<String>(2);
try {
System.out.println(queue.add("a")); // true
System.out.println(queue.add("b")); // true
System.out.println(queue.add("c")); // java.lang.IllegalStateException: Queue full
}catch (Exception e){
e.printStackTrace();
}finally {
queue.clear();
}
System.out.println(queue.offer("a")); // true
System.out.println(queue.offer("b")); // true
System.out.println(queue.offer("c")); // false
}
}
通过上面演示可以看出,add、remove、element三个方法在操作过程中,如果访问超过了容器的固定存储范围,那么会报异常,而另一组offer、poll、peek则会给出一种特殊的返回值,不会报错。
查看Queue源码,发现继承了Collection,从这里也可以初步知道Queue实际是一个集合,对应的api也是针对集合的一个扩展
public interface Queue<E> extends Collection<E> {
boolean add(E e);
boolean offer(E e);
E remove();
E poll();
E element();
E peek();
}
PriorityQueue是Queue的一种实现,而Queue是一种规范,通过Queue中api简单操作,初步了解PriorityQueue
public class PriorityQueueTest {
public static void main(String[] args) {
PriorityQueue<Integer> queue = new PriorityQueue<Integer>();
System.out.println("入队前数据:[5, 3, 2, 4, 7]");
queue.add(5);
System.out.println("入队第一个数据后的队列:" + queue);
queue.add(3);
System.out.println("入队第二个数据后的队列:" + queue);
queue.add(2);
System.out.println("入队第三个数据后的队列:" + queue);
queue.add(4);
System.out.println("入队第四个数据后的队列:" + queue);
queue.add(7);
System.out.println("全部入队后的队列:" + queue);
System.out.println("移除第一个数据: " + queue.remove());
System.out.println("移除第一个数据后的队列:" + queue);
System.out.println("移除第二个数据: " + queue.remove());
System.out.println("移除第二个数据后的队列:" + queue);
System.out.println("移除第三个数据: " + queue.remove());
System.out.println("移除第三个数据后的队列:" + queue);
System.out.println("移除第四个数据: " + queue.remove());
System.out.println("移除第四个数据后的队列:" + queue);
System.out.println("移除最后个数据: " + queue.remove());
System.out.println("移除最后个数据后的队列:" + queue);
}
}
入队前数据:[5, 3, 2, 4, 7]
入队第一个数据后的队列:[5]
入队第二个数据后的队列:[3, 5]
入队第三个数据后的队列:[2, 5, 3]
入队第四个数据后的队列:[2, 4, 3, 5]
全部入队后的队列:[2, 4, 3, 5, 7]
移除第一个数据: 2
移除第一个数据后的队列:[3, 4, 7, 5]
移除第二个数据: 3
移除第二个数据后的队列:[4, 5, 7]
移除第三个数据: 4
移除第三个数据后的队列:[5, 7]
移除第四个数据: 5
移除第四个数据后的队列:[7]
移除最后个数据: 7
移除最后个数据后的队列:[]
根据打印结果,入队或出队后,会把每次队列中最小数放在队首。而且出队的时候数据依次增大。这就是PriorityQueue一个特点:根据队列中数据以某种优先级进行将数据出队。
如何实现呢?查看源码
// 查看入队源码
// queue实际是一个数组
transient Object[] queue;
public boolean add(E e) {
return offer(e);
}
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1);// 扩展容器长度
size = i + 1;
if (i == 0)
queue[0] = e; // 首个元素入队
else
siftUp(i, e);
return true;
}
private void siftUp(int k, E x) {
if (comparator != null)// 自定义比较器不为空
siftUpUsingComparator(k, x); // 使用自定义比较器的进行入队
else
siftUpComparable(k, x); // 使用默认比较器的进行入队
}
private void siftUpComparable(int k, E x) {
Comparable super E> key = (Comparable super E>) x;
while (k > 0) {
int parent = (k - 1) >>> 1; // 取队首元素
Object e = queue[parent];
if (key.compareTo((E) e) >= 0) // 使用入队元素的默认比较器,与队首元素比较
break;
queue[k] = e; // 发现比队首元素小,则与队首元素替换
k = parent;
}
queue[k] = key;
}
@SuppressWarnings("unchecked")
private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
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;
}
对入队源码的分析知道,每次入队操作都会根据比较器进行判断,选择合适元素放在队首,所以添加的队列数据必须实现Comparator接口或者实例化Priority Queue 提供一个自定义比较器
在看看出队操作。同时add的内部实现是通过offer实现的,
public E remove() {
E x = poll(); // 出队
if (x != null)
return x;
else
throw new NoSuchElementException();
}
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;
}
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
@SuppressWarnings("unchecked")
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>)x;
int half = size >>> 1; // loop while a non-leaf
while (k < half) {
int child = (k << 1) + 1; // assume left child is least
Object c = queue[child];
int right = child + 1;
// 取相邻中最小数
if (right < size &&
((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
c = queue[child = right];
// 最小数与目标数对比,目标数小,则退出
if (key.compareTo((E) c) <= 0)
break;
queue[k] = c;
k = child;
}
//将最小数放置队首
queue[k] = key;
}
@SuppressWarnings("unchecked")
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}
出队与入队当数据变动后,就会计算出最新数据放到队首,
可以总结PriorityQueue一些特点: