
问题
- 什么是优先级队列?
- 怎么实现一个优先级队列?
- PriorityQueue是线程安全的吗?
- PriorityQueue就有序的吗?
简介
优先级队列,是0个或多个元素的集合,集合中的每个元素都有一个权重值,每次出队都弹出优先级最大或最小的元素。
一般来说,优先级队列使用堆来实现。
那么Java里面是如何通过“堆”这个数据结构来实现优先级队列的呢?
让我们一起来学习吧。
源码分析
主要属性
1 2 3 4 5 6 7 8 9 10 11
| private static final int DEFAULT_INITIAL_CAPACITY = 11;
transient Object[] queue;
private int size = 0;
private final Comparator<? super E> comparator;
transient int modCount = 0;
|
- 默认容量是11;
- queue,元素存储在数组中,这跟我们之前说的堆一般使用数组来存储是一致的;
- comparator,比较器,在优先级队列中,也有两种方式比较元素,一种是元素的自然顺序,一种是通过比较器来比较;
- modCount,修改次数,有这个属性表示PriorityQueue也是fast-fail的;
入队
入队有两个方法,add(E e)和offer(E e),两者是一致的,add(E e)也是调用的offer(E e)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| 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); }
@SuppressWarnings("unchecked") 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; }
|
- 入队不允许null元素;
- 如果数组不够用了,先扩容;
- 如果还没有元素,就插入下标0的位置;
- 如果有元素了,就插入到最后一个元素往后的一个位置(实际并没有插入哈);
- 自下而上堆化,一直往上跟父节点比较;
- 如果比父节点小,就与父节点交换位置,直到出现比父节点大为止;
- 由此可见,PriorityQueue是一个小顶堆。
扩容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| private void grow(int minCapacity) { int oldCapacity = queue.length; int newCapacity = oldCapacity + ((oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1)); if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); queue = Arrays.copyOf(queue, newCapacity); }
private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
|
- 当数组比较小(小于64)的时候每次扩容容量翻倍;
- 当数组比较大的时候每次扩容只增加一半的容量;
出队
出队有两个方法,remove()和poll(),remove()也是调用的poll(),只是没有元素的时候抛出异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| public E remove() { E x = poll(); if (x != null) return x; else throw new NoSuchElementException(); }
@SuppressWarnings("unchecked") 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; 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; }
|
- 将队列首元素弹出;
- 将队列末元素移到队列首;
- 自上而下堆化,一直往下与最小的子节点比较;
- 如果比最小的子节点大,就交换位置,再继续与最小的子节点比较;
- 如果比最小的子节点小,就不用交换位置了,堆化结束;
- 这就是堆中的删除堆顶元素;
取队首元素
取队首元素有两个方法,element()和peek(),element()也是调用的peek(),只是没取到元素时抛出异常。
1 2 3 4 5 6 7 8 9 10
| public E element() { E x = peek(); if (x != null) return x; else throw new NoSuchElementException(); } public E peek() { return (size == 0) ? null : (E) queue[0]; }
|
如果有元素就取下标0的元素;
如果没有元素就返回null,element()抛出异常;
总结
PriorityQueue是一个小顶堆;
PriorityQueue是非线程安全的;
PriorityQueue不是有序的,只有堆顶存储着最小的元素;
入队就是堆的插入元素的实现;
出队就是堆的删除元素的实现;
彩蛋
- 论Queue中的那些方法?
Queue是所有队列的顶级接口,它里面定义了一批方法,它们有什么区别呢?
| 操作 |
抛出异常 |
返回特定值 |
| 入队 |
add(e) |
offer(e)——false |
| 出队 |
remove() |
poll()——null |
| 检查 |
element() |
peek()——null |
- 为什么PriorityQueue中的add(e)方法没有做异常检查呢?
因为PriorityQueue是无限增长的队列,元素不够用了会扩容,所以添加元素不会失败。