上次写完ArrayList,直接把linkedList计划取消了。过后想了想,这是我最大的弱点了,还是得写。然后找了PriorityQueue,看了源码,继续写吧。
1.继承结构
先看一张PriorityQueue,继承结构的图吧。
相比较ArrayList,PriorityQueue并没有实现Cloneable,暂时不是很明白,需要继续学习。
Collection主要描述的特征和能力,AbstractCollection和AbstractQueue也不多介绍了。Queue,一般都是想到先进先出,LinkedList根据这个特性实现的,但是是根据某种特性,先出队的。看一下Queue的主要方法,相比较的话主要是多了offer,
pull,peek方法,顾名思义,就是从后面推进去,从前面拉出来,还有偷看第一个元素。Queue在结构上应该是变简单的了的,
毕竟LinkedList在实现List的基础上就实现了Deque,Deque是Queue的加强版。
属性
看源码的时候,首先应该看的是他的属性,属性一般是它的具体实现的数据结构,PriorityQueue的属性有queue,size,modeCount,comparator。
首先来看queue,是一个Object[],说明PriorityQueue是由数组实现的,具体怎么实现的下面再说。
size,是它的大小,应该数组的大小并不代表容器类里面元素的数量,size还作为边界。
comparator,用于确定元素比较的顺序,规则必不可少,不然无法确定优先级。
modeCount,PriorityQueue并不是线程安全的,作为一个标签,记录是否发生了异常。
通过这些属性,可以明白PriorityQueue主要是通过comparator将数据按照一定规则储存在数组里面,size标记元素在数组
中的数量。
构造方法
构造方法决定了PriorityQueue在初始化时的状态,PriorityQueue构造方法特别多,但是多重载。
属性构造方法:
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
可以看到,主要是初始化了queue和comparator,size属性使用的是默认值0。所以构造方法主要作用是给属性赋值。
capacity的作用是用于数组的扩容。为什么要扩容,当然是为了合理利用空间。
Colletion构造方法:
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);
}
}
这个构造方法可以说明问题。
给comparator赋值,重新排列数据。
这里主要有两个方法,initElementFromCollection和initFromCollection。
private void initElementsFromCollection(Collection<? extends E> c) {
Object[] a = c.toArray();
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;
}
private void initFromCollection(Collection<? extends E> c) {
initElementsFromCollection(c);
heapify();
}
initElementFromCollection相当于是数据拷贝。initFromCollection是先进行数据拷贝,然后堆化。
实现原理
了解完特征之后,再来了解怎么实现的。PriorityQueue有什么特点,每次poll的都是我优先级最高的元素,比方说,每次取都取
到最小的元素。数组的结构就是第一个元素都是最小元素,如果一棵树上没条路劲都是递增的,那么root节点就是最小元素了,而二
叉堆就是满以二叉树的数组形式进行保存。
先了解下二叉堆的特点,p代码父节点,lf代表左子节点,rt代表右子节点。
p = (lf -1) >> 1; lf = p << 1 + 1; rt = p << 1 + 2;
主要方法
看完属性,构造器,接下来应该看的add,remove方法,这里是分别对应offer,poll方法,还有heapify方法。
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;
else
siftUp(i, e);
return true;
}
首先看offer方法,做了哪些事,判断不能为null,结构改变+1,如果容量不够就扩容,size+1,如果容量大于1,调用siftUp方法进行插入元素。
siftUp方法:
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, 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;
}
这里区分了有没有传入comparator,其实只是就是需要一个规则,没有传入就调用元素的的Comparable接口进行排序。
具体实现就是假设把元素放到最后一个位置,然后再找到一个位置的路劲,对比路径上的元素,使这条路径按规则排序。
poll方法:
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;
}
poll方法稍微复杂一点点。
具体实现就是,删掉第一个元素。
假设把最后一个元素放到第一个位置。这里假设以大小排序,父节点总是小于子节点,如果父节点大于子节点中一个或一个以上的元素的时候,和较小的元素替换位置,以此类推,保证结构继续保持有序。
heapify方法:
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
要明白这个方法只需要搞明白一点,满二叉树,只要有一般是叶子节点,就是说,剩下的有子节点,要保持结构有序,只要从下网上逐层调用siftDown方法即可,想不到更好的办法,脑子一片空白,只能接受,读书太少,就别想太多,想了也没用。
还有一个方法是removeAt,稍微也可以注意下。
了解完这些方法剩下的方法,应该都很简单了。其实主要要了解的是二叉树,满二叉树,二叉堆。
迭代器
属性:
cursor:当前迭代器指向堆的对象的数组的坐标;
lastRet:表示迭代器刚刚越过的元素;
expectedModCount:修改次数;
forgetMeNot:表示迭代过程中遗漏的元素;
lastRetElt:下一个遗漏的元素。
如果不存在forgetMeNot,lastRetElt应该是很容易理解的。那怎么理解这两个属性呢,删除元素的时候,调用的是removeAt
方法,再用最后一个元素替换的时候,如果最后一个元素往上移动了,将不会再遍历到,则放入forgetMeNot中进行存储。正常遍
历完了之后再进行遍历。