碰到一个场景,我们要是实现一个任务调度系统,但是呢任务是分等级的,也就是说比较高级的任务必须优先执行,那么这个任务调度系统需要怎么设计呢?不可避免的想到了优先级队列,下面我看看一下Java提供的优先级队列:priorityQueue。
根据上面的继承关系,可以看到PriorityQueue也实现了Collection的接口,不免让人想到List。
什么是队列,队列的性质什么的这里不再介绍,有点编程知识的都应该知道。
优先级队列,既然是优先级,那么内部肯定有一定的规则进行排序,并每一次出队列的保证是优先级最高的。
优先级队列的用法实际上很简单,需要实现是一个比较器,内部根据这个比较器来判定谁的优先级比较高:
public class MyComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return -(o1.getId() - o2.getId());
}
}
用起来也很简单:
public static void main(String[] args) {
Queue<Integer> queue = new PriorityQueue();
queue.add(1);
queue.add(2);
queue.add(5);
queue.add(3);
while (!queue.isEmpty()){
System.out.println(queue.poll());
}
Queue<Student> queue1 = new PriorityQueue(new MyComparator());
Student st1 = new Student("ming1", 10);
Student st2 = new Student("ming2", 11);
Student st3 = new Student("ming3", 9);
Student st4 = new Student("ming4", 20);
Student st5 = new Student("ming5", 1);
queue1.add(st1);
queue1.add(st2);
queue1.add(st3);
queue1.add(st4);
queue1.add(st5);
while (!queue1.isEmpty()){
System.out.println(queue1.poll().getId());
}
}
打印出来的结果:
1
2
3
5
20
11
10
9
1
那我们看下优先级队列的源码,优先级队列的实现实际上按照堆的思想来进行的,如果不了解堆的话,可以先复习一下树的相关知识:
他有好多种构造方法:
transient Object[] queue; // non-private to simplify nested class access
private static final int DEFAULT_INITIAL_CAPACITY = 11;
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
上面只列出了一个构造方法,实际他有很多的,但是最后都会调到这个方法里面来 ,进行队列的初始化,可以看到默认的大小是11,当然你可以不用传初始化的数组的大小与比较器。
我们来看向队列中添加的方法:
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;
}
//实现了自己的比较器
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;
}
上面的代码最重要的是,插入数据中能保证堆的结构不变,插入数据的时候都在最后,但是在siftUpComparable 方法中,对数据根据比较器的结果对数据进行上移,保证第一个永远是优先级最高的。
出队列的时候, 没出一个元素,堆的优先级就需要调整,进队的时候是从下向上的调整,那么出队列正好相反,从上往下调整:
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; // 这个是拿到左节点
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;
}
上面的代码中,首先拿到的是第一个节点的左节点,和又节点,在我们进队列的时候,就已经保证了父节点的优先级一定高于子节点,但是并没有保证左节点一定比右节点的的优先级高,所以需要比价左右节点的优先级。
还有一点的是扩容:
private void grow(int minCapacity) {
int oldCapacity = queue.length;
// Double size if small; else grow by 50%
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);
}
扩容也很简单,如果当前的容量小于64,扩大两倍,如果大于64,扩大原来容量的1/2.
有一点需要注意的是,PriorityQueue不是线程安全的,如果多线程的话 则需要另外一个优先级队列: PriorityBlockingQueue 实现和PriorityQueue是差不多的,区别是在进队和出队的时候加了锁 lock.lock();
欢迎关注我的微信号: manong_xiaodong