Queue
首先簡單認識下Queue,查看API定義了6個方法,歸納爲新增、刪除、查詢三種不同操作,每種操作有2個不同的實現,基於JDK 1.8
api接口如下:
新增接口
- boolean add(E e)
- boolean offer(E e)
查詢:
- E element()
- E peek()
刪除:
- E remove()
- E poll()
每種操作有不同的實現,他們區別是什麼?實戰操作
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
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一些特點:
- 1、入隊元素必須實現Comparator比較器接口,或者用戶自己提供比較器,否則無法使用
- 2、PriorityQueue通過數組實現,數組長度雖然固定,但可以擴容,
- 3、因爲可以擴容add不會存在容器長度問題報異常,而remove和element仍然存在此問題
- 4、PriorityQueue是非線程安全的,新增、刪除操作沒有加鎖操作
PriorityQueue應用場景
- 處理很多任務且任務有不同的執行級別