写在前面,最近准备春招找实习,还有项目要做,一直没更新博客。不过通过这次找实习总结出来一个结论,基础很重要。 唉,这次找实习就当试试水了,总结一下经验,打好基础。秋招再战~
JDK源码一个学长也说了,算是集大家之精华,读了有益无害哈~
一、 优先队列的内部原理
优先队列,通过名字就应该知道它内部是按照一定的优先关系进行元素的排列。而优先队列默认内部采用小根堆维护着一个一维数组。
至于什么是堆,先说一下定义:
堆是一棵完全二叉树,并且满足一定排序关系:父亲节点比它的任意子节点大的称作大根堆,父节点比它的任意节点小的称作小根堆。从这可以知道根节点要么最小要么最大。
知道了什么是堆,下面就需要思考一下,当一个元素插入或者删除的时候,怎么才能让其满足小根堆(大根堆)条件,维护一个优先关系。以下拿小根堆举例子
节点的插入:
1、将节点插入到树的最后
2、沿着插入位置逐步与父节点比较,如果比父节点小交换位置,如此继续下去,直到大于等于父节点停止
节点的删除
1、将指定位置的节点删除,将队尾最后的叶子节点插入到删除的位置
2、如果当前删除位置子女节点不为空,则与其子女节点比较,如果大于其子女,向下交换位置,如此继续,直到小于等于其子女节点。
3、如果发现删除位置节点小于其父亲节点,向上交换位置,如此继续,知道大于等于其父节点停止。
二、源码阅读
首先,优先队列采用一个一维数组存储元素,用这个数组表示堆(也市二叉树)。在默认情况下这个数组的大小为11。另外一个构造函数传入大小以及比较方法(用于进行元素大小比较)。
private Object[] queue;
private static final int MAX_ARRAY_SIZE=Integer.MAX_VALUE-8;
public SimplePriorityQueue(){
this(DEFAULT_INITIAL_CAPACITY,null);
}
public SimplePriorityQueue(int size,Comparator<? super E> comparator) {
if (size < 1)
throw new IllegalArgumentException();
this.comparator = comparator;
this.queue=new Object[size];
}
紧接着我们直接看元素添加操作,
public boolean add(E e){
return offer(e);//调用offer方法
}
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;//第一个元素直接放在0位置,也是堆的根节点
}
else
siftUp(i,e);//放在堆的最后,然后向上调整
return true;
}
/**
* 选择二叉树堆的比较策略
* 如果设置了自定义Comparator,则使用自定义Comparator
* 未设置使用默认比较
* @param k
* @param e
*/
private void siftUp(int k, E e) {
if (comparator != null){
siftUpUsingComparator(k, e);
}
else
siftUpComparable(k, e);
}
/**
* 采用小根堆维护顺序
* 规则:将新插入的元素放在最后,然后与它的父节点进行比较。
* 如果比父节点小,则交换位置。这样继续下去,找到合适的位置
* @param k
* @param 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;
}
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;
}
通过这些我们就可以总结出来,在进行添加元素的时候,先判断是否超出一维数组的大小,如果超出进行扩容,否则,按照小根堆添加元素策略加入元素。
元素删除
//以下的代码都一样,进行元素的删除,都是调用同一个方法reomvaAt
/**
* 删除指定位置的元素
* 含义:如果删除i位置的节点之后,最后面的叶子节点在经过
* 与当前节点位置下面节点(sifeDown)排序后,发现还在i位置,表示比下面的节点小。这时候需要再判断一下
* 是否当前节点与上面的节点需要调整,查看是否比上面的还小(sifeUp)
* 当之前的叶子节点调整到当前位置的上一级节点后,就需要返回这个节点。
* @param i
* @return
*/
private E removeAt(int i){
modCount++;
int s= --size;
if (s==i){
queue[i]=null;
}
else{
E moved= (E) queue[s];
queue[s]=null;
siftDown(i,moved);
if (queue[i]==moved){
siftUp(i,moved);
if (queue != moved){
return moved;
}
}
}
return null;
}
boolean removeEq(Object o){
for (int i=0;i< size; i++){
if (o==queue[i]){
removeAt(i);
return true;
}
}
return false;
}
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;
}
先说这么多吧,可能发现有一个modCount,每次进行添加或者删除之后都会自增,其实它是在优先队列的迭代器中使用的,防止在多线程情况下,一个线程向优先队列中添加了元素,另外一个线程这时候还在迭代之前没有添加元素旧的迭代器中迭代。当发生这种情况时候,直接抛出异常,添加还好说,如果是删除如果不采用这种方式可能就会产生数组越界的异常。
就写这么多了,如果有不对的,请大佬们指出~