JDK源码阅读 - 04 集合 之 Queue

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方法添加元素时的处理流程:

  1. 要求被添加的元素不能为null,否则抛出异常;
  2. 若是当前队列里的元素个数大于等于队列底层数组的长度时,执行数组扩容方案:
    1. 如果当前数组长度小于64时,那么扩容的目标数组长度 = 当前数组长度 * 2 + 2;否则扩容的目标长度 = 当前数组长度 * 1.5;
    2. 如果目标数组长度 > Integer.MAX_VALUE - 8 (2147483639),那么就执行最后一次扩容:那么扩容数组最终长度应该是 Interger.MAX_VALUE
  3. 如果当前是个空数组,那么元素直接放在数组第一个位置,否则根据比较器进行处理
    1. 优先构造参数传进来的比较器,其次才是元素自己的比较器
    2. 当前新增元素的索引位置为K,(旧数组元素个数 - 1)除以 2,得到最中间元素的索引位置 M。
    3. 如果新增的元素 通过比较器 大于等于中间元素,那么中间元素就放在索引为K的位置,跟新K值 等于M值。否则新元素就放K位置,比较结束。
    4. 继续进行元素比较,如果新增比较起来足够大,那么它最终会放在数组索引为0的位置,其他比它小的元素都在它后面排着。
    5. 上述代码按从小到大排列,最小的就会放在前面,但是比它大的元素并不会按顺序排列。

通过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;
}
  1. 先把数组的头部元素和尾部元素拿出来,头部元素作为方法返回对象,尾部元素位置设置为null,数组长度减一,传递代表移除元素的索引 K = 0;
  2. 计算当前数组剩余长度 / 2 得到的中位索引,从(K=0)到中位索引进行循环比较
  3. 从数组的第(K+1) 位元素child对象与右边相邻(k + 1 +1)的元素right对象进行比较。根据比较器结果,大于0才将child对象改为right对象;
  4. 若尾部元素此时与child对象进行比较,若小于等于当前child对象,则终止循环比较 (此时尾部元素
  5. 当循环停止时,将尾部元素放在此时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。

基于此索引位置会做如下处理:

  1. 如果此索引位置就是原数组最后一个元素位置,那么这个位置直接设置null;return null结束;
  2. 否则基于此索引位置作为基准,把数组的最后一个元素拿过来,并将最后元素位置设置为null。用最后一个元素和此索引位置,执行上述poll逻辑的(2、3、4、5)步骤;
  3. 执行完毕后,如果指定要移除的索引位置元素此时刚好是尾部元素,那么此索引位置 和尾部元素,可看作是对数组新添加了元素,执行offer逻辑的第3步的(b、c、d、e)步骤;
  4. 执行完毕后,如果指定要移除的索引位置元素此时刚好是尾部元素,return null;否则 return 这个尾部元素。

说明:第3、4步可以看作是把原数组分成了两段,后一段进行数据获取和移除(第3步),前一段是数据添加到队列(第4步)

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值