如何在Java中实现高效的去重优先队列
发布时间:2025-05-21 08:43:05 发布人:远客网络
一、如何在Java中实现高效的去重优先队列
1、在Apache IoTDB中,查询最后需要根据时间戳列做join,这一关键步骤通过一个带有自动去重功能的优先队列实现。然而,使用Java自带的`TreeSet`进行操作时,我们遇到了性能问题。鉴于此,我们自主设计了一个高效的去重优先队列。相较于Java默认的`PriorityQueue`,它在处理`long`类型数据时存在装箱与拆箱的开销,而这些额外步骤消耗了CPU时间和内存资源。具体而言,一个`long`型整数占用8字节内存,但`Long`对象至少还需包含4字节的对象头。若还需在优先队列的基础上实现去重功能,避免重复元素存在于堆中,Java标准库中只能通过`TreeSet`实现,尽管它基于红黑树,为了保持树的平衡和数据的全序性,涉及了复杂的旋转操作,这在实际场景中并不必要。同样地,`TreeSet`也以泛型编程方式实现,导致了装箱与拆箱的性能损耗。
2、为了克服这些限制,我们设计了`TimeSelector`工具类,将其实现在`server/src/main/java/org/apache/iotdb/db/utils/datastructure`包下。这一类内置了一个`long[]`数组,利用该数组进行操作,可以有效处理特定场景下的需求。在`percolateUp`和`percolateDown`方法中,我们添加了去重检查逻辑,当尝试插入的元素与当前元素相同时,直接返回,避免不必要的操作,从而达到去重的目的。请参考以下示例代码。
3、为了对比不同实现的性能差异,我们执行了插入10,000,000个`long`型随机数,并计算所有数之和的实验。实验结果显示,与`TimeSelector`相比,`PriorityQueue`的执行时间慢了32%,而`TreeSet`则慢了51%。
二、数据结构——优先队列
优先队列顾名思义,就是优先权最大的排在队列的头部,而优先权的判断是根据对象的compare方法比较获取的,保证根节点的优先级一定比子节点的优先级大。所以放入到优先队列的元素要么实现了Comparable接口,要么在创造这个优先队列时,指定一个比较器。
普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出(first in, largest out)的行为特征。通常采用堆数据结构来实现。
在Java中也实现了自己的优先队列 java.util.PriorityQueue,与我们自己写的不同之处在于,Java中内置的为最小堆,然后就是一些函数名不一样,底层还是维护了一个Object类型的数组,大家可以戳戳看有什么不同,另外如果想要把最小堆变成最大堆可以给PriorityQueue传入自己的比较器。
三、看图说话之二叉堆(优先队列)——原理解析
优先队列更多的是一种逻辑上和业务上的设计。队列中的每项元素都分配一个优先权值,队列的出队顺序按照优先权值来划分,高优先权先出队或者低优先权先出队。这样一个优先队列必须具备两个最基本的操作——入队,以及高(低)优先权出队。优先队列在很多场景中都有运用,比如打印机的任务调度和操作系统中的进程调度都有用到优先队列的设计。
此时我们假设并不知道有堆这样的数据结构,想实现上述这样的功能,其实也可以有多种解决方案,比如以下两种:
二叉堆如其名字一样和二叉树具有紧密的关系。二叉堆是一种特殊的二叉树,是对一般的二叉树提出了结构性和堆序性的要求,这种特殊结构性和堆序性是二叉堆的性质所在。
1.结构性:二叉堆是一个完全二叉树
2.堆序性:所有的节点值均小于(大于)其后裔节点值,若所有节点值大于其后裔节点这样的二叉堆称为大根堆##点值均小于其后裔节点这样的二叉堆成为小根堆。
这里可以看到,假如是小根堆的情况,那么每次取堆顶的元素,就完成了按低优先级出队的功能。若是大根队取堆顶的元素则完成按高优先级出对的顺序。
这里需要特别注意就是,每次的出队操作=返回堆顶元素+删除堆顶元素,每次删除堆顶元素之后,需要保证依旧满足二叉堆的结构性和对堆序性,这个删除和调整的过程称为堆调整。
下面以大根堆为例进行介绍一次堆调整,并且分析其时间复杂度为什么是O(logN)。下文描述中,堆的删除操作=优先队列的出队,堆的插入操作=优先队列的入队。
上文介绍了大根堆的出队操作,其时间复杂度在O(logN),此处介绍大根堆的入队操作,入队操作和出队操作和其相似,最坏时间复杂度也是O(logN)。下面将通过图例的形式分析大根堆的入队操作,假设插入元素是19。
2.如图9所示,在插入元素之后二叉堆的结构特性已经满足了,下面主要需要调整堆序特性。对于元素19而言,找到其父亲元素8,判断堆序性,发现19大于8,所以交换19和8的位置,结果如下。
[1]数据结构与算法 java语言描述版