天天看點

PriorityQueue源碼分析PriorityQueue源碼分析

PriorityQueue源碼分析

重點知識羅列

  • PriorityQueue 采用數組的形式儲存資料,邏輯上采用二叉堆儲存

    有關二叉堆的實作原理,檢視本人博文 Ted 帶你學習資料結構 之 二叉堆

  • PriorityQueue 數組排序并非按照插入順序,而是需要根據比較器判斷; 插入自定義對象時,自定義對象需要實作Comparable接口 ,或者使用外部比較器對象,外部比較器對象需實作Compartor接口

    關于比較器詳細介紹,可參考本人博文ompareable接口和Compartor接口對比分析

  • PriorityQueue 不能加入null值
  • PriorityQueue 插入和删除原理代碼實作

代碼選取jdk1.8

① PriorityQueue 屬性

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;  
           

由上可知 : PriorityQueue 裡面維護了一個Object數組對象 ,且默大小為11

② 構造器

public PriorityQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}

public PriorityQueue(int initialCapacity,
 Comparator<? super E> comparator) {

if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
} 
           

由上可知: 構造一個 PriorityQueue對象,按照預設11個尺寸建立Object[] 數組,且如果沒有傳入比較器Comparator對象,為null ;

③ 添加對象

public boolean add(E e) {
return offer(e);
}

 public boolean offer(E e) {
if (e == null)
throw new NullPointerException();  // 這是該對象不能加入null值最好例證
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;
}  
           

上面方法中,主要調了grow()方法,和sitUp()方法,我們深入研究下

private void grow(int minCapacity) {
int oldCapacity = queue.length;

int newCapacity = oldCapacity + ((oldCapacity < 64) ?
 (oldCapacity + 2) :
 (oldCapacity >> 1));  

 // 當小于64時,表示堆高在6層内時,每次擴容為原來的1倍+2 ,當大于6層時,每次擴容原來的50%    
 // 層高計算方式為 H=log(Length) ,但當length為2的次方時,H=log(Length)+1
 //上面64,因為是2的6次方,代入到公式中為log64+1 =7,但為為是<号,是以實則表示6層,未到7層

if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
queue = Arrays.copyOf(queue, newCapacity);
} 
           
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
} 
           

siftUp()方法,需要判斷有沒有比較器,我們就以siftUpUsingComparator(k, x)為例

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;
} 
           

這段代碼,有點意思,我們來細撸一下

通過 (k-1)>>>1,位移運算符,獲得父節點的索引,然後得到它的值 Object e ,比較父節點e和傳入的對象x,如果,子節點比父節點大,那麼,break跳出while循環 ; 但如果子節點值不如父節點,那麼,它們的值交換位置,并且,此時,仍在while循環裡面,會層層和父節點比較,如果發生父節點比子節點大的情況,會一直交換值,直到對象x,到達堆頂

PriorityQueue源碼分析PriorityQueue源碼分析

(4)删除對象

poll方法的原理, 從堆頂冊除對象a,并且堆中最末位b對象替換堆頂值,然後下沉排序,(拿到根節點下的左右節點,并且判斷左右節點的大小,b如果值>子節點,和子節點中中最小值進行替換,循環)

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);
}

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;
} 
           

(5)取值

public E peek() {
return (size == 0) ? null : (E) queue[0];
}
           

取值方法,很好了解了,就是拿到堆頂的值。

PriorityQueue源碼分析PriorityQueue源碼分析