Queue
接口,继承Collection接口,并进行了方法扩展
- offer,向队列中添加一个元素
- remove、poll,获取并移除队列头部元素,两者的区别是remove在获取不到时会抛出异常。poll得到空元素
- element、peek,获取头部元素,两者的区别是element获取不到数据时会抛出异常。
AbstractQueue
继承AbstractCollection、并实现Queue接口。
AbstractQueue是抽象类,是队列的部分操作逻辑实现的模板方法类。
PriorityQueue
继承AbstractQueue,优先级队列,长度无限,但是内部底层是个数组,默认的初始数组长度是11,但是可通过构造方法传入指定的初始长度。
PriorityQueue 队列要求添加进入的元素对象实现了Comparable接口,或者通过构造方法创建队列对象时,参数传入了指定的比较器。
PriorityQueue 队列会对添加进去的元素根据比较器排序,会把比较结果小的元素放在队列的头部,大的元素放在尾部。(相对小或大,根据比较器的实现方式选择)。
public static void testPriorityQueue(){
Cat cat1 = new Cat(7, "seven");
Cat cat2 = new Cat(5,"five");
Cat cat3 = new Cat(9,"nine");
Cat cat4 = new Cat(5,"five2");
Comparator<Cat> ageComparator = Comparator.comparing(Cat::getId);
Queue<Cat> priorityQueue = new PriorityQueue<>(ageComparator);
priorityQueue.add(cat1);
System.out.println(priorityQueue);
priorityQueue.add(cat2);
System.out.println(priorityQueue);
priorityQueue.add(cat3);
System.out.println(priorityQueue);
priorityQueue.add(cat4);
System.out.println(priorityQueue);
}
输出结果:
[Cat{id=7, name='seven'}]
[Cat{id=5, name='five'}, Cat{id=7, name='seven'}]
[Cat{id=5, name='five'}, Cat{id=7, name='seven'}, Cat{id=9, name='nine'}]
[Cat{id=5, name='five'}, Cat{id=5, name='five2'}, Cat{id=9, name='nine'}, Cat{id=7, name='seven'}]
通过add、offer方法添加元素时的处理流程:
- 要求被添加的元素不能为null,否则抛出异常;
- 若是当前队列里的元素个数大于等于队列底层数组的长度时,执行数组扩容方案:
- 如果当前数组长度小于64时,那么扩容的目标数组长度 = 当前数组长度 * 2 + 2;否则扩容的目标长度 = 当前数组长度 * 1.5;
- 如果目标数组长度 > Integer.MAX_VALUE - 8 (2147483639),那么就执行最后一次扩容:那么扩容数组最终长度应该是 Interger.MAX_VALUE
- 如果当前是个空数组,那么元素直接放在数组第一个位置,否则根据比较器进行处理
- 优先构造参数传进来的比较器,其次才是元素自己的比较器
- 当前新增元素的索引位置为K,(旧数组元素个数 - 1)除以 2,得到最中间元素的索引位置 M。
- 如果新增的元素 通过比较器 大于等于中间元素,那么中间元素就放在索引为K的位置,跟新K值 等于M值。否则新元素就放K位置,比较结束。
- 继续进行元素比较,如果新增比较起来足够大,那么它最终会放在数组索引为0的位置,其他比它小的元素都在它后面排着。
- 上述代码按从小到大排列,最小的就会放在前面,但是比它大的元素并不会按顺序排列。
通过poll方法获取头部元素后,需要调整数组里其他元素:
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;
}
- 先把数组的头部元素和尾部元素拿出来,头部元素作为方法返回对象,尾部元素位置设置为null,数组长度减一,传递代表移除元素的索引 K = 0;
- 计算当前数组剩余长度 / 2 得到的中位索引,从(K=0)到中位索引进行循环比较
- 从数组的第(K+1) 位元素child对象与右边相邻(k + 1 +1)的元素right对象进行比较。根据比较器结果,大于0才将child对象改为right对象;
- 若尾部元素此时与child对象进行比较,若小于等于当前child对象,则终止循环比较 (此时尾部元素
- 当循环停止时,将尾部元素放在此时K索引的位置。
因为队列在添加数据时,保证了比较结果大于等于0的元素都放在数组左边,所以只比较到1/2位置处就可以了。
尾部元素是个锚定对象,用来提前终止比较。
public static void testPriorityQueue(){
Cat cat1 = new Cat(7, "seven");
Cat cat2 = new Cat(5,"five");
Cat cat3 = new Cat(9,"nine");
Cat cat4 = new Cat(5,"five2");
Cat cat5 = new Cat(3,"five2");
Cat cat6 = new Cat(8,"five2");
Cat cat7 = new Cat(12,"five2");
Comparator<Cat> ageComparator = Comparator.comparing(Cat::getId);
Queue<Cat> priorityQueue = new PriorityQueue<>(ageComparator);
priorityQueue.add(cat1);
priorityQueue.add(cat2);
priorityQueue.add(cat3);
priorityQueue.add(cat4);
priorityQueue.add(cat5);
priorityQueue.add(cat6);
priorityQueue.add(cat7);
System.out.println(priorityQueue);
for (int i=0;i<7;i++){
Cat poll = priorityQueue.poll();
System.out.println("移除的对象:" +poll + "\t剩余对象:" + priorityQueue);
}
}
输出结果:
[Cat{id=3, name='five2'}, Cat{id=5, name='five'}, Cat{id=8, name='five2'}, Cat{id=7, name='seven'}, Cat{id=5, name='five2'}, Cat{id=9, name='nine'}, Cat{id=12, name='five2'}]
移除的对象:Cat{id=3, name='five2'}剩余对象:[Cat{id=5, name='five'}, Cat{id=5, name='five2'}, Cat{id=8, name='five2'}, Cat{id=7, name='seven'}, Cat{id=12, name='five2'}, Cat{id=9, name='nine'}]
移除的对象:Cat{id=5, name='five'}剩余对象:[Cat{id=5, name='five2'}, Cat{id=7, name='seven'}, Cat{id=8, name='five2'}, Cat{id=9, name='nine'}, Cat{id=12, name='five2'}]
移除的对象:Cat{id=5, name='five2'}剩余对象:[Cat{id=7, name='seven'}, Cat{id=9, name='nine'}, Cat{id=8, name='five2'}, Cat{id=12, name='five2'}]
移除的对象:Cat{id=7, name='seven'}剩余对象:[Cat{id=8, name='five2'}, Cat{id=9, name='nine'}, Cat{id=12, name='five2'}]
移除的对象:Cat{id=8, name='five2'}剩余对象:[Cat{id=9, name='nine'}, Cat{id=12, name='five2'}]
移除的对象:Cat{id=9, name='nine'}剩余对象:[Cat{id=12, name='five2'}]
移除的对象:Cat{id=12, name='five2'}剩余对象:[]
移除指定元素
优先级队列数组移除指定元素,先会遍历底层数组,找到这个元素的第一个索引位置 i。
基于此索引位置会做如下处理:
- 如果此索引位置就是原数组最后一个元素位置,那么这个位置直接设置null;return null结束;
- 否则基于此索引位置作为基准,把数组的最后一个元素拿过来,并将最后元素位置设置为null。用最后一个元素和此索引位置,执行上述poll逻辑的(2、3、4、5)步骤;
- 执行完毕后,如果指定要移除的索引位置元素此时刚好是尾部元素,那么此索引位置 和尾部元素,可看作是对数组新添加了元素,执行offer逻辑的第3步的(b、c、d、e)步骤;
- 执行完毕后,如果指定要移除的索引位置元素此时刚好是尾部元素,return null;否则 return 这个尾部元素。
说明:第3、4步可以看作是把原数组分成了两段,后一段进行数据获取和移除(第3步),前一段是数据添加到队列(第4步)