Java PriorityQueue源码分析

news/2024/5/19 0:16:05 标签: java, PriorityQueue

文章目录

  • 类继承关系
    • Queue
    • AbstractQueue
  • PriorityQueue源码分析
    • grow方法
    • offer方法
    • poll方法
    • remove方法
  • 总结

Java API 地址:
https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/util/PriorityQueue.html

jdk源码下载地址
https://gitee.com/huangtianyu/jdk-source

参考:
https://www.cnblogs.com/wangziqiang123/p/11697080.html

https://blog.csdn.net/qq_41786692/article/details/80302254

https://www.jianshu.com/p/dc0eeb82e994

Queue(队列)是拥有先进先出(FIFO)特性的数据结构,PriorityQueue(优先级队列)是它的子类之一,不同于先进先出,它可以通过比较器控制元素的输出顺序(优先级)。

类继承关系

API文档中给出了继承关系,如下图:
在这里插入图片描述

Queue

先来看Queue接口:

java">public interface Queue<E> extends Collection<E>

Queue接口继承了Collection接口,表示集合。它提供了三种方法,即:增加、删除、获取,每种都有两个实现。

java">// 增加元素
boolean add(E e);
boolean offer(E e);
// 删除元素
E remove();
E poll();
// 获取元素
E element();
E peek();

AbstractQueue

java">public abstract class AbstractQueue<E>
    extends AbstractCollection<E>
    implements Queue<E> {

AbstractQueue继承了AbstractCollection类和实现了Queue接口。既然是模板类那肯定有模板方法。AbstractQueue源码中实现了add、remove和elemet方法。

java">public boolean add(E e) {
        if (offer(e)) // 调用offer
        ...
    }
    
public E remove() {
        E x = poll(); // 调用poll
        ...
    }
    
public E element() {
        E x = peek(); // 调用peek
        ...
    }

PriorityQueue_70">PriorityQueue源码分析

PriorityQueue是通过“极大优先级堆”实现的,即堆顶元素是优先级最大的元素。算是集成了大根堆和小根堆的功能。

根据堆的特性,存储结构肯定是数组了;既然支持不同优先级,肯定有比较器,也就是说支持自定义排序和顺序排序。

java">   private final Comparator<? super E> comparator;

grow方法

PriorityQueue是支持扩容的,先来看扩容方法:

java">    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);
    }

每次扩展的容量大小还是挺大的,要么变为原来的双倍,要么增长一半大小。

在看增加元素、删除元素和获取元素的方法之前,先了解以下几点:

  • 完全二叉树的最后一个非叶子结点的下标是 (n-2) / 2;
  • 完全二叉树中如果一个非叶子结点的下标是i,则它的父结点下标是(i-1)/2,它的左孩子下标是 2 * i + 1,右孩子下标是 2 * i + 2;

offer方法

java">   public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        modCount++;
        int i = size;
        if (i >= queue.length)
            grow(i + 1); // 如果超出当前容量,则进行扩容
        siftUp(i, e); // 新元素都是增加在数组尾部,然后进行上移操作,即构建堆
        size = i + 1; // 当前大小加1
        return true;
    }

siftUp方法最终会调用siftUpUsingComparator或者siftUpComparable方法,两个实现类似,这里直接看siftUpUsingComparator方法。

java">// 上移就是不断和父结点比较
private static <T> void siftUpUsingComparator(
        int k, T x, Object[] es, Comparator<? super T> cmp) {
        while (k > 0) {
            int parent = (k - 1) >>> 1; // 父结点下标
            Object e = es[parent];
            if (cmp.compare(x, (T) e) >= 0) // 优先级高则继续上移比较
                break;
            es[k] = e;
            k = parent;
        }
        es[k] = x;
    }

每次增加元素,都要保证堆序。

poll方法

java">public E poll() {
        final Object[] es;
        final E result;
        // 返回堆顶元素
        if ((result = (E) ((es = queue)[0])) != null) {
            modCount++;
            final int n;
            final E x = (E) es[(n = --size)]; // 把尾部元素换到第一个
            es[n] = null;
            if (n > 0) {
                final Comparator<? super E> cmp;
                if ((cmp = comparator) == null) // 自然序时,下移调整
                    siftDownComparable(0, x, es, n);
                else // 自定义序下移调整
                    siftDownUsingComparator(0, x, es, n, cmp);
            }
        }
        return result;
    }

poll方法会返回队首元素(堆顶),并将元素从堆中删除。删除过程,是将第一个元素与最后一个元素进行替换,然后再进行下移调整操作。

remove方法

poll方法可以看出是remove方法的特例,即固定删除第一个元素。

java">public boolean remove(Object o) {
        int i = indexOf(o); // 找到待删除元素位置
        if (i == -1)
            return false;
        else {
            removeAt(i); // 删除指定位置元素
            return true;
        }
    }

调用了removeAt方法:

java">E removeAt(int i) {
        // assert i >= 0 && i < size;
        final Object[] es = queue;
        modCount++;
        int s = --size; // size已经减1
        if (s == i) // removed last element
            es[i] = null; // 已经删除到最后一个元素
        else {
            E moved = (E) es[s]; // 尾元素
            es[s] = null;
            siftDown(i, moved); // 指定元素换尾元素,然后调整
            if (es[i] == moved) {
                siftUp(i, moved); // 如果指定位置换成了尾元素(没有发生下移)则进行上移操作
                if (es[i] != moved)
                    return moved;
            }
        }
        return null; // 正常删除时返回null
    }

总结

  1. PriorityQueue是基于最大优先级堆实现的,根据比较器的情况可以是大根堆或者小根堆;
  2. PriorityQueue不支持null;
  3. PriorityQueue不是线程安全的,多线程环境下可以使用java.util.concurrent.PriorityBlockingQueue
  4. 使用iterator()遍历时,不保证输出的序列是有序的,其实遍历的是存储数组。

http://www.niftyadmin.cn/n/1838556.html

相关文章

Dolphin:KDE 中的文件操持器

Toy Posted in AppsDolphin 与 Konqueror 所走的是一条分歧的路径&#xff0c;它次要专注于 KDE 桌面情况中的文件操持&#xff0c;不像后者除了文件操持之外&#xff0c;也可作为网站阅读工具&#xff0c;乃至还有很多其他功能。在这个意义上&#xff0c;Dolphin 从优化用户界…

SQL里怎么用Like搜时间字段

SQL数据表中有savetime(smalldatetime类型)字段,表中有两条记录,savetime值为:2005-3-8 12:12:00和2005-6-6 14:02:02我用下面语句什么也搜不出来select * from soft where soft.savetime like%2005-3-8%SQL帮助中说:"当搜索 datetime 值时&#xff0…

第5课-哈希表、映射、集合

文章目录Hash table工程实践Hash FunctionHash Collisions完整结构复杂度分析Java codePython codeMap, Set : interfaces复杂度分析实战题目Hash table 哈希表(Hash table)&#xff0c;也叫散列表&#xff0c;是根据关键码值(Key value)而直接进行访问的数据结构。 它通过把…

『转载』SMS常见FAQ

最近公司普查盗版软件&#xff0c;真的是删到手软&#xff0c;而且被ADMIN强推了SMS到电脑上&#xff0c;号称可以由网管部门协助进行正版事业&#xff08;现在看来只不过是广告噱头罢了&#xff09;&#xff0c;于是那台P4 1.8G终于扛不住了&#xff0c;经常CPU 100%&#xff…

WCF 第十一章 工作流服务

在本书阐述到此时&#xff0c;你已经知道了WCF就是关于服务定义&#xff0c;服务创建和服务安全的。服务契约中有规范化描述的定义的很好的边界&#xff0c;但是从服务外面看&#xff0c;内部工作是完全不透明的。WCF 描述了很少的一部分服务实现&#xff1b;它简单地提供了接口…

第6课-树、二叉树、二叉搜索树

文章目录树 Tree二叉树 Binary Tree图 Graph示例代码二叉树遍历 Pre-order/In-order/Post-order示例代码二叉搜索树 Binary Search Tree二叉搜索树 Binary Search Tree二叉搜索树常见操作复杂度分析树的面试题解法一般都是递归示例代码示例代码树的遍历 DEMO实战题目树 Tree 二…

期待的几本书

&#xff08;1&#xff09;Understanding Linux Network Internals 东南大学出版社要出影印版&#xff08;2&#xff09;Code Quality : The Open Source Perspective Code Reading 的姊妹篇&#xff08;3&#xff09;Managing in the Modular Age: Architectures, Networks, a…

UTL_FILE 的用法

UTL_FILE 是用来进行文件IO处理的专用包,使用这外包的注意事项如下:1. 生成的文件好象只能放置在DATABASE所在的服务器路径中.2. 生成的文件如何DOWNLOAD到本地来,还有待研究.Coding步骤:1. 注册文件输出路径Create directory path[例如: C:\AA] as pathname;此命令应由数据库管…