Java 集合框架3:Queue
Queue
1.概述
Queue 是一个保持将要处理元素的集合,基于 Collection 的方法,还提供添加、抽取、读取方法,而这些方法都有两个版本,一个是针对有长度限制的实现而可能抛出异常的版本,另一个是针对无长度限制的实现而不会抛出异常的版本:
Queue 通常以 FIFO 形式组织元素, stack 以 LIFO 形式组织元素。所以,Queue 的实现需要指定排序规则,使用自然排序或者提供 comparator。
无论以 FIFO 还是 LIFO 组织元素,Queue 的 remove 和 poll 均移除 head 元素,element 和 peek 也是返回 head 元素。
Queue 的某些类允许插入 null 元素,比如 ArrayList。但使用时,应该不允许插入 null,以区分 Queue 方法中不会抛出异常而是返回 null 的函数的调用情况。
2.BlockingQueue
Queue 并未定义阻塞方法,但 Queue 的子接口 BlockingQueue 提供了阻塞。
BlockingQueue 提供 4 种方法,前两种与 Queue 一样,第 3 种会阻塞当前的线程直到操作可以成功,最后一种则是阻塞一段时间,如果没有成功就放弃操作。
BlockingQueue 不支持 null 元素,主要原因是 null 元素在 poll 方法中用作哨兵。
BlockingQueue 主要用于生产者-消费者队列。
BlockingQueue 是线程安全的,所有排队方法以原子方式实现其效果,但是 bulk 操作(addAll、containsAll、retainAll 和 removeAll)不一定以原子操作进行。
BlockingQueue 不支持关闭操作(close、shutdown),通常使用插入特殊值(end-of-stream、poison objects)来表明队列关闭。
使用示例
生产者-消费者问题:
class Producer implements Runnable { private final BlockingQueue queue; Producer(BlockingQueue q) { queue = q; } public void run() { try { while (true) { queue.put(produce()); } } catch (InterruptedException ex) { ... handle ...} } Object produce() { ... } } class Consumer implements Runnable { private final BlockingQueue queue; Consumer(BlockingQueue q) { queue = q; } public void run() { try { while (true) { consume(queue.take()); } } catch (InterruptedException ex) { ... handle ...} } void consume(Object x) { ... } } class Setup { void main() { BlockingQueue q = new SomeQueueImplementation(); Producer p = new Producer(q); Consumer c1 = new Consumer(q); Consumer c2 = new Consumer(q); new Thread(p).start(); new Thread(c1).start(); new Thread(c2).start(); } }
3.Deque
Deque,被称之为双端队列,是 Queue 的子接口。与 Queue 不同的是,它支持从两端进行元素的操作。
与 Queue 一样,Deque 也有两种形式的方法,一种针对于有大小限制的实现,会抛出异常,另一种针对于无大小限制的实现而返回 null 或其他值。
Deque 使用可以退化为 Queue,遵循 FIFO,方法对应关系如下:
Deque 也可以退化为 stack,遵循 LIFO,方法对应如下:
除此之外,Deque 还提供了 removeFirstOccurrence 和 removeLastOccurrence 方法,用于 remove 队列中第一次出现和最后一次出现的指定元素。
Deque 并不支持像 List 那样的索引访问。Deque 不推荐去添加 null 元素,因为 null 元素被指示为队列为空。
4.实现
Queue 的通用实现有 LinkedList 和 PriorityQueue,LinkedList 也是 Deque 的通用实现。(LinkedList 实现参见我的另外一篇博客:Java 集合框架2:List。)
对于 Queue 的并发实现,在 java.util.concurrent 包中,主要有 LinkedBlockingQueue、ArrayBlockingQueue。
Deque 的通用实现还有 ArrayDeque,并发实现有 LinkedBlockingDeque。
PriorityQueue
PriorityQueue 基于堆实现,并且没有边界限制。
PriorityQueue 的迭代器顺序并不保证,如果需要,使用 Arrays 的 sort 方法:
PriorityQueue 并不保证线程安全,如果需要,使用 PriorityBlockingQueue。
实现原理
PriorityQueue 实现基于平衡二叉树,并且采用小顶堆,表明父节点不大于子节点。
以 offer 方法为例,添加元素,首先需要保证有足够的空间,没有则调用 grow 方法进行扩容。然后调用 siftUp 函数添加元素:
扩容策略为小长度时变为 2 倍,否则增长 50%:
siftUp 会根据采用定制排序还是自然排序而调用相应的函数:
这里以自然排序为例,如果新添节点比父节点大,就在新添元素指定的 index 位置 k 添加,否则 k 位置会被填入它的父节点,然后 k 更新为父节点的位置,继续与其该位置的父节点进行比较,直到满足新添元素不小于 k 的父节点为止。这里有一个巧妙的地方是,如果 k 最终变为了根节点,也就是 0,那么它的 parent 也会是 0,此时相等,新元素填充。
对于删减函数,以 remove 为例。
对于 remove 函数,首先会在数组中找寻第一个指定的元素的 index,随后调用 removeAt 函数对对应 index 进行删除:
removeAt 函数中,如果删除的是数组最后一个元素,直接将最后以后一个元素置为 null,并返回。否则,使用 moved 记录最后一个元素,将最后一个元素置为 null 后,需要对原二叉树重新构造,构造调用了 siftDown 函数。
siftDown 函数根据排序规则选择不同的函数,这里以自然排序时调用 siftDownComparable 方法为例,siftDownComparable 进行了第一次结构修改,这一步修改只保证删除位置的节点,也就是 i(siftDownComparable 中为 k),是不大于子节点的。
由于底层的二叉树是完全二叉树,那么 size 的一半(该 size 已经在 removeAt 函数中减 1),也就是 half,大于等于它的节点为叶节点。
倘若 k(也就是要 remove 元素在数组中的索引)节点为叶节点(k >= half),那么直接将该叶节点换为 key(也就是原数组中最后一个元素,保存在 moved 的那个)。
倘若 k 不为叶节点(k < half),会进行 while 循环。k 需要和它的最小子节点进行比较(这里先假设了左子节点更小,然后和右节点比较),c 保存了更小的节点。k 不大于 c,表明满足第一次结构修改,则退出循环,将 k 位置填充为 key。如果 k 大于 c,那么 k 和 c 互换,继续循环,直到 k 到达叶节点或 k 不大于 c 时为止。
siftDownComparable 执行完成后,返回到 removeAt 函数,执行第二次结构修改,这里修改的目的是保证删除位置的节点,也就是 i(不同于 siftDownComparable 中的 k,这个 i 是原本删除元素的位置),是不小于父节点的。这里调用了 offer 方法同样会调用到的 siftUp 方法。
LinkedBlockingQueue
LinkedBlockingQueue 是一种可选容量限制的基于链表实现的阻塞队列。
LinkedBlockingQueue 通常比 ArrayBlockingQueue 有更多的修改操作,但更少的查询操作。
LinkedBlockingQueue 包含一个空头节点,默认的容量大小为 Integer.MAX_VALUE。LinkedBlockingQueue使用了 head 和 last 分别指向空头节点和尾节点。
以 put 方法为例,先对添加元素进行上锁,这里上锁采用了 lockInterruptibly 方法,意味着 put 方法支持中断,如果在获取 putLock 的过程中,线程被中断,那么将会抛出 InterruptedException。随后,循环等待队列不为空,循环等待在 count 不被上锁时也能使用,因为我们之前上锁了 put,队列中的元素不会增加。等待到 notFull 后,进行入队 enqueue,然后 put 解锁。在此过程中,c 的作用是便于发送队列不为空等信号。
添加了时间限制参数的 offer 方法与之类似,只是在 notFull 的 await 过程中,判断是否超时:
LinkedBlockingQueue 提供弱一致性的迭代器,因为,获取 iterator 的实例化 Itr 的过程中,进行上锁时,可能其它线程正在使用,等待过程中元素可能已经发生了改变。
ArrayBlockingQueue
ArrayBlockingQueue 是一个基于数组的 Bounded Buffer 实现。
ArrayBlockingQueue 的阻塞实现类似于 LinkedBlockingQueue,只不过它操作的是数组而不是链表。
需要注意的是,存储元素用的数组 items 是用 final 修饰的,意味着 ArrayBlockingQueue 的大小在构造时指定,且不能改变。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构