java1.8 常用集合源码学习:PriorityQueue

1、api
一个基于优先级堆的无界优先级 队列 。优先级队列的元素按照其 自然顺序 进行排序,或者根据构造队列时提供的  Comparator  进行排序,具体取决于所使用的构造方法。优先级队列不允许使用  null  元素。依靠自然顺序的优先级队列还不允许插入不可比较的对象(这样做可能导致  ClassCastException )。
此队列的  是按指定排序方式确定的 最小  元素。如果多个元素都是最小值,则头是其中一个元素——选择方法是任意的。队列获取操作  poll remove peek  和  element  访问处于队列头的元素。
优先级队列是无界的,但是有一个内部 容量 ,控制着用于存储队列元素的数组大小。它通常至少等于队列的大小。随着不断向优先级队列添加元素,其容量会自动增加。无需指定容量增加策略的细节。
此类及其迭代器实现了  Collection  和  Iterator  接口的所有 可选  方法。方法  iterator()  中提供的迭代器  保证以任何特定的顺序遍历优先级队列中的元素。如果需要按顺序遍历,请考虑使用  Arrays.sort(pq.toArray())
注意,此实现不是同步的。 如果多个线程中的任意线程修改了队列,则这些线程不应同时访问  PriorityQueue  实例。相反,请使用线程安全的  PriorityBlockingQueue  类。
实现注意事项:此实现为排队和出队方法( offer poll remove()  和  add )提供 O(log(n)) 时间;为  remove(Object)  和  contains(Object)  方法提供线性时间;为获取方法( peek element  和  size )提供固定时间。
此类是  Java Collections Framework  的成员。

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来维护这部分已经上移的数据,在正常遍历结束后再遍历这部分数据





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值