1、api
一个基于优先级堆的无界优先级
队列
。优先级队列的元素按照其
自然顺序
进行排序,或者根据构造队列时提供的
Comparator
进行排序,具体取决于所使用的构造方法。优先级队列不允许使用
null
元素。依靠自然顺序的优先级队列还不允许插入不可比较的对象(这样做可能导致
ClassCastException
)。
此队列的
头
是按指定排序方式确定的
最小
元素。如果多个元素都是最小值,则头是其中一个元素——选择方法是任意的。队列获取操作
poll
、
remove
、
peek
和
element
访问处于队列头的元素。
优先级队列是无界的,但是有一个内部
容量
,控制着用于存储队列元素的数组大小。它通常至少等于队列的大小。随着不断向优先级队列添加元素,其容量会自动增加。无需指定容量增加策略的细节。
此类及其迭代器实现了
Collection
和
Iterator
接口的所有
可选
方法。方法
iterator()
中提供的迭代器
不
保证以任何特定的顺序遍历优先级队列中的元素。如果需要按顺序遍历,请考虑使用
Arrays.sort(pq.toArray())
。
实现注意事项:此实现为排队和出队方法(
offer
、
poll
、
remove()
和
add
)提供 O(log(n)) 时间;为
remove(Object)
和
contains(Object)
方法提供线性时间;为获取方法(
peek
、
element
和
size
)提供固定时间。
2、源码学习
默认的初始化容量为11
private static final int
DEFAULT_INITIAL_CAPACITY
=
11
;
存储数据的数组
transient
Object[]
queue
;
维护队列大小
private int
size
=
0
;
私有的比较器,如果为null,则使用元素本身的自然顺序排序
private final
Comparator<?
super
E
>
comparator
;
结构修改的次数
transient int
modCount
=
0
;
从一个已有集合创建PriorityQueue,如果这个集合是可排序的,则使用他的comparator,然后调用三个不同的方法将PriorityQueue初始化
public
PriorityQueue
(Collection<?
extends
E
> c) {
if
(c
instanceof
SortedSet<?>) {
SortedSet<?
extends
E
> ss = (SortedSet<?
extends
E
>) c
;
this
.
comparator
= (Comparator<?
super
E
>) ss.comparator()
;
initElementsFromCollection(ss)
;
}
else if
(c
instanceof
PriorityQueue<?>) {
PriorityQueue<?
extends
E
> pq = (PriorityQueue<?
extends
E
>) c
;
this
.
comparator
= (Comparator<?
super
E
>) pq.comparator()
;
initFromPriorityQueue(pq)
;
}
else
{
this
.
comparator
=
null;
initFromCollection(c)
;
}
}
首先调用c.toArray是因为有些实现了排序的集合的这个方法返回的是排序好的数组,如果调用这个方法返回的不是Object数组,则使用Arrays.copyOf拷贝一份Object数组。然后检查数组的非空性,这里有一个疑问,为什么判断len == 1的情况。最后将这个数组的值和长度赋给当前PriorityQueue的queue和size使用
private void
initElementsFromCollection
(Collection<?
extends
E
> c) {
Object[] a = c.toArray()
;
// If c.toArray incorrectly doesn't return Object[], copy it.
if
(a.getClass() != Object[].
class
)
a = Arrays.
copyOf
(a
,
a.
length
,
Object[].
class
)
;
int
len = a.
length
;
if
(len ==
1
||
this
.
comparator
!=
null
)
for
(
int
i =
0
;
i < len
;
i++)
if
(a[i] ==
null
)
throw new
NullPointerException()
;
this
.
queue
= a
;
this
.
size
= a.
length
;
}
其中首先调用了上面的initElementsFromCollection方法,然后调用了heapify方法将数据二叉堆化,这个方法我们先放放,看完其他方法最后再分析他
private void
initFromCollection
(Collection<?
extends
E
> c) {
initElementsFromCollection(c)
;
heapify()
;
}
插入队列的方法,首先排除null值。然后判断队列大小,如果大或等于内部维护的数组大小,则调用grow方法增加数组的长度,然后size计数加1 ,并且调用siftUp方法将元素存入数组中,这个siftUp方法也放在后面看
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;
}
grow方法和hugeCapacity方法一起看,grow方法判断旧的数组长度,如果小于64,则类似于翻倍,如果大于64,则每次增长50%,如果增长后的数组长度大于允许的最大值,则调用hugeCapacity方法重置一下增长后的数组长度为允许的最大值,该方法中minCapacity < 0的情况是针对前面扩大数组长度时有可能会导致溢出问题使int变成了负值
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)
;
}
private static int
hugeCapacity
(
int
minCapacity) {
if
(minCapacity <
0
)
// overflow
throw new
OutOfMemoryError()
;
return
(minCapacity >
MAX_ARRAY_SIZE
) ?
Integer.
MAX_VALUE
:
MAX_ARRAY_SIZE
;
}
indexOf方法是遍历数组
private int
indexOf
(Object o) {
if
(o !=
null
) {
for
(
int
i =
0
;
i <
size
;
i++)
if
(o.equals(
queue
[i]))
return
i
;
}
return
-
1
;
}
removeEq方法也是遍历数组判断要删除的对象的引用是否在数组中,如果在,则调用removeAt来删除,removeAt也在后面说
boolean
removeEq
(Object o) {
for
(
int
i =
0
;
i <
size
;
i++) {
if
(o ==
queue
[i]) {
removeAt(i)
;
return true;
}
}
return false;
}
poll方法首先将queue数组的头取出作为最后的返回值。然后将数组的最后一位取出,调用siftDown方法重新存入数组中,siftDown方法也放在后面统一看
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
;
}
下面开始看sift相关方法,由于内部的数组是一个二叉树,需要提前学习一下二叉树相关知识的话可以看这两个博客
首先看删除的过程。如果要删除的位置是队列的尾,则直接删除。如果不是,则将队列尾元素取出,并从要删除的位置下虑,如果这个元素又移动到了队列尾,则方法结束。如果元素停在了i位置没有移动,则需要上移。上述的下虑和上移是为了将尾元素从新放入队列,并删除原位置的元素。这里如果尾元素确实进行了上移,则需要将该元素返回,这是为了迭代时候使用(因为在迭代中还没有迭代到数组这个尾元素)。
private
E
removeAt
(
int
i) {
// assert i >= 0 && i < size;
modCount
++
;
int
s = --
size
;
if
(s == i)
// removed last element
queue
[i] =
null;
else
{
E
moved = (
E
)
queue
[s]
;
queue
[s] =
null;
siftDown(i
,
moved)
;
if
(
queue
[i] == moved) {
siftUp(i
,
moved)
;
if
(
queue
[i] != moved)
return
moved
;
}
}
return null;
}
siftDown根据有没有comparator分为两个方法,这两个方法类似,挑一个看
private void
siftDown
(
int
k
,
E
x) {
if
(
comparator
!=
null
)
siftDownUsingComparator(k
,
x)
;
else
siftDownComparable(k
,
x)
;
}
这个方法下虑给定的的x,传入的k值最初一般是x在数组中的位置
取得k位置的元素的儿子元素(有可能是两个),取其中更小的一个和x相比较,如果x比这个儿子大,则将儿子移动到位置k,如果x比这个儿子大则退出循环
将儿子的位置赋值给k,然后继续上述操作,直到k值大于数组长度的一半(这时候k的位置已经是叶子了,无需再判断),然后将x放到k的当前位置
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
;
}
这时候我们重新看一下heapfy方法,实际上是从数组一半的位置开始循环下虑每一个元素,直至数组头,这样就将数组重新排列成了一个二叉树
private void
heapify
() {
for
(
int
i = (
size
>>>
1
) -
1
;
i >=
0
;
i--)
siftDown(i
,
(
E
)
queue
[i])
;
}
上移操作是在插入时使用的,也是分为两个方法,我们看其中一个
private void
siftUp
(
int
k
,
E
x) {
if
(
comparator
!=
null
)
siftUpUsingComparator(k
,
x)
;
else
siftUpComparable(k
,
x)
;
}
上移方法比较简单,x为待插入数组的元素,k最初传入时是数组最后一个位置的索引
取得位置k的父亲位置,判断该位置元素和x的大小,如果x更小,则将父元素放到k位置,否则退出循环
将父元素的位置赋值给k,继续上述操作,直至k=0,然后将x赋值给当前的k值
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
;
}
spliterator相关的以后统一说
最后还有它的迭代器类Itr,这个类和其他的没什么太大区别,只是在迭代过程中有可能会上移数据尾元素,如果数据上移过,需要保证迭代器不会遗失这部分数据,于是使用了forgetMeNot来维护这部分已经上移的数据,在正常遍历结束后再遍历这部分数据