常用数据结构-队列 (Queue)


🚀 队列 (Queue)


🔎 特点

  • FIFO: 先进先出(FIFO)数据结构,队列中的元素按照它们被添加的顺序进行检索。
  • 尾进头出: 从一端(队尾)进入,并从另一端(队头)出来
  • 有界与无界: 队列可以是有界的(限定大小)或无界的(大小由内存限制)。
  • 支持并发操作: 部分队列支持多线程并发操作,如添加、删除等。
  • 阻塞与非阻塞: 队列操作可以是阻塞的或非阻塞的。

🔍 Java中常用的队列及其类型

  • LinkedList: 实现了 Deque 接口,可以作为双端队列使用。
  • PriorityQueue: 一个基于优先级的队列。
  • ArrayDeque: 双端队列,更高效地使用内存。
  • ConcurrentLinkedQueue: 一个非阻塞并发队列。
  • LinkedBlockingQueue: 一个基于链表的阻塞队列。
  • ArrayBlockingQueue: 一个基于数组的有界阻塞队列。
  • SynchronousQueue: 一个不存储元素的阻塞队列。
  • DelayQueue: 使用优先级队列实现的阻塞队列。

📌 有界队列 (Bounded Queue)

  • 🎯 特点:

    • 📎 有界队列是一个固定大小的队列。
    • 📎 当队列满时,任何尝试入队的新元素都会被阻塞或抛出异常,直到有空间为止。
    • 📎 当队列为空时,尝试出队的操作也可能被阻塞或返回一个特殊值。
  • 🔍 结构:
    通常实现为一个数组或循环数组。

  • 🛠️ 使用场景:

    • 📎 当需要限制队列大小以控制内存使用时。
    • 📎 在生产者-消费者模型中,当生产者和消费者的处理速度有差异时使用。
  • 📌 使用方式:

    import java.util.concurrent.ArrayBlockingQueue;
    
    ArrayBlockingQueue<Integer> boundedQueue = new ArrayBlockingQueue<>(10); // 创建一个容量为10的有界队列
    
    boundedQueue.put(1); // 添加元素; 当队列满时,此方法会阻塞
    boundedQueue.take(); // 获取并移除队列的头部; 当队列为空时,此方法会阻塞
    
  • ⚠️ 注意: 超出容量的操作可能导致阻塞或异常,这取决于具体的实现方式。


📌 无界队列 (Unbounded Queue)

  • 🎯 特点:

    • 📎 无界队列没有固定的大小限制。
    • 📎 在理论上,它可以无限增长,受到的限制只是可用的内存。
  • 🔍 结构:
    通常实现为链表。

  • 🛠️ 使用场景:

    • 📎 在任务的数量无法预先确定或者有大量的短暂任务需要处理时。
    • 📎 用于线程池,例如ThreadPoolExecutor中的任务队列。
  • 📌 使用方式:

    import java.util.concurrent.LinkedBlockingQueue;
    
    LinkedBlockingQueue<Integer> unboundedQueue = new LinkedBlockingQueue<>(); // 创建一个无界队列
    
    unboundedQueue.put(1); // 添加元素
    unboundedQueue.take(); // 获取并移除队列的头部; 当队列为空时,此方法会阻塞
    
  • ⚠️ 注意: 虽然无界队列在理论上是无限的,但实际上它受到物理内存的限制。


📌 优先队列 (Priority Queue)

  • 🎯 特点:

    • 📎 元素出队的顺序是根据其优先级,而不是它们进入队列的顺序。
    • 📎 可以有多种实现方式,但最常见的是使用堆。
  • 🔍 结构:
    堆是一种常见的优先队列的实现。

  • 🛠️ 使用场景:

    • 📎 用于任务调度,其中任务有不同的优先级。
    • 📎 Dijkstra’s算法,其中需要按照节点的最短距离选择下一个节点。
  • 📌 使用方式:

    import java.util.PriorityQueue;
    
    PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
    
    priorityQueue.add(3);
    priorityQueue.add(1);
    priorityQueue.add(2);
    
    System.out.println(priorityQueue.poll()); // 输出: 1
    
  • ⚠️ 注意: 优先队列不保证同等优先级元素的顺序。

在Java的PriorityQueue中,元素的优先级是通过以下方式定义的:

  1. 自然排序: 如果队列中的元素类实现了Comparable接口,那么元素之间的优先级是按照compareTo方法定义的自然顺序来确定的。

  2. 使用比较器 (Comparator): 如果队列的元素类没有实现Comparable接口或者你想提供一个与自然顺序不同的排序方式,你可以为PriorityQueue提供一个Comparator对象。这个Comparator对象的compare方法定义了元素之间的优先级。

  3. 自然排序 (使用 Comparable):

class Task implements Comparable<Task> {
    private int priority;

    public Task(int priority) {
        this.priority = priority;
    }

    @Override
    public int compareTo(Task other) {
        return Integer.compare(this.priority, other.priority);
    }
}

PriorityQueue<Task> queue = new PriorityQueue<>();
  1. 使用 Comparator:
class Task {
    private int priority;

    public Task(int priority) {
        this.priority = priority;
    }

    public int getPriority() {
        return priority;
    }
}

Comparator<Task> taskComparator = new Comparator<Task>() {
    @Override
    public int compare(Task t1, Task t2) {
        return Integer.compare(t1.getPriority(), t2.getPriority());
    }
};

PriorityQueue<Task> queue = new PriorityQueue<>(taskComparator);

在这两种情况下,当从队列中拉取任务时,优先级较高的任务(即优先级值较小的任务,因为上述示例中的比较方法使用的是自然的整数顺序)将首先被处理。


📌 延迟队列 (Delayed Queue)

  • 🎯 特点:

    • 📎 只有当元素的预定时间到达时,它才能从队列中被取出。
    • 📎 元素必须定义它们的延迟策略。
  • 🔍 结构:
    通常实现为一个优先队列,其中优先级是基于元素的延迟时间。

  • 🛠️ 使用场景:

    • 📎 任务调度,当任务需要在特定的未来时间执行时。
    • 📎 缓存系统,当需要在指定时间后自动删除过期的缓存项。
  • 📌 使用方式:

    import java.util.concurrent.DelayQueue;
    import java.util.concurrent.Delayed;
    import java.util.concurrent.TimeUnit;
    
    class DelayedElement implements Delayed {
        private long delayTime;
        private long expireTime;
    
        public DelayedElement(long delayTime) {
            this.delayTime = delayTime;
            this.expireTime = System.currentTimeMillis() + delayTime;
        }
    
        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(expireTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }
    
        @Override
        public int compareTo(Delayed o) {
            return Long.compare(this.getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS));
        }
    }
    
    DelayQueue<DelayedElement> delayQueue = new DelayQueue<>();
    delayQueue.put(new DelayedElement(5000)); // 5秒后到期的元素
    
    // 获取并移除队列的头部; 如果没有到期的元素,此方法会阻塞
    delayQueue.take();
    
  • ⚠️ 注意: 尽管元素被排队,但直到其预定的延迟时间到达之前,它都不能被消费。


🌍 常用场景

  • 任务调度: 使用优先级队列来调度任务的执行。
  • 数据流处理: 在多线程环境中,生产者-消费者模型常用队列来传递数据。
  • 并发控制: 使用阻塞队列来控制并发访问数量。

🔧 工具类的使用

1️⃣ Apache Commons Collections:

Apache Commons Collections 提供了一系列增强的数据结构和算法,对于队列来说,它扩充了 Java SDK 提供的队列功能。

  • CircularFifoQueue: 是一个有界的循环FIFO队列,当它满了之后,新元素会覆盖最旧的元素。
CircularFifoQueue<String> queue = new CircularFifoQueue<>(3);
queue.add("A");
queue.add("B");
queue.add("C");
queue.add("D"); // This will remove A from the queue
  • PredicatedQueue: 允许你添加一个断言到队列中,只有当元素满足断言时才能被添加。
Predicate<Object> predicate = o -> (o instanceof String);
Queue<String> queue = PredicatedQueue.predicatedQueue(new LinkedList<>(), predicate);

2️⃣ Google Guava:

Google Guava 是另一个功能丰富的 Java 库,它提供了一些队列的增强功能。

  • EvictingQueue: 是一个固定大小的队列,当达到其最大大小时,新元素的加入会导致老元素被移除。这与 CircularFifoQueue 类似,但提供了更多的功能。
EvictingQueue<String> queue = EvictingQueue.create(3);
queue.add("1");
queue.add("2");
queue.add("3");
queue.add("4");  // This will cause "1" to be evicted

⚠️ 注意: 以上的工具类并不能代替Java SDK中的队列,而是为特定的应用场景提供了额外的功能。在选择时,应当基于实际需求来选择最合适的工具类。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yueerba126

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值