在 Rust 中使用队列

更新于 2026-01-15

Basillica 2024-03-27

队列在许多程序中是一种非常有用的数据结构。它允许你将元素添加到队列的一端,并从另一端移除元素,遵循先进先出(FIFO)的顺序。在这篇博客文章中,我将提供一份完整的指南,介绍如何在 Rust 中实现和使用队列。

什么是队列?

队列是一种线性数据结构,允许将元素添加到一端(称为“队尾”),并从另一端(称为“队首”)移除元素。元素被移除的顺序与其被添加的顺序相同,从而实现了 FIFO(先进先出)的排序方式。

这种排序方式使得队列在需要按到达顺序处理元素时非常有用。常见的应用场景包括:

  • 任务处理 —— 将任务添加到队尾,然后从队首依次处理
  • 打印任务排队 —— 将打印作业加入队列,打印机从队首取出作业进行处理
  • Web 请求处理 —— 按照请求到达的顺序接收并处理请求

队列与栈不同,栈是 LIFO(后进先出)的结构;队列也不同于优先队列,后者出队顺序取决于优先级,而不仅仅是到达顺序。

在 Rust 中实现队列

Rust 的标准库中没有内置的队列数据结构。不过,我们可以使用 Vec 作为底层存储来实现一个基本的队列。

下面是一个简单的队列实现:

struct Queue {
    data: Vec<i32>
}

impl Queue {
    fn new() -> Self {
        Queue { data: Vec::new() }
    }
    
    fn enqueue(&mut self, item: i32) {
        self.data.push(item);
    }
    
    fn dequeue(&mut self) -> Option<i32> {
        self.data.pop()
    }
    
    fn peek(&self) -> Option<&i32> {
        self.data.first()
    }
    
    fn len(&self) -> usize {
        self.data.len()
    }
    
    fn is_empty(&self) -> bool {
        self.data.is_empty()
    }
}

该实现提供了一个基本的队列,包含用于添加和删除元素的 enqueuedequeue 方法、用于查看下一个元素的 peek 方法,以及用于检查队列状态的 lenis_empty 方法。

我们使用 Rust 的 Option 枚举作为 dequeuepeek 的返回类型,以处理队列为空的情况。

队列操作

让我们看看如何使用这个队列实现:

let mut queue = Queue::new();

// 添加一些元素
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3); 

// 检查值    
assert_eq!(queue.peek(), Some(&1));
assert_eq!(queue.len(), 3);

// 移除一个元素
assert_eq!(queue.dequeue(), Some(1));

// 检查结果
assert_eq!(queue.peek(), Some(&2));  
assert_eq!(queue.len(), 2);

这展示了基本的队列操作:

  • Enqueue(入队) —— 将元素添加到队尾
  • Dequeue(出队) —— 从队首移除元素
  • Peek(窥视) —— 查看下一个将被出队的元素
  • Len(长度) —— 获取当前队列长度

此外,还有 is_emptylen 等方法用于检查队列的状态。

处理下溢(Underflow)

我们当前的队列实现在对空队列调用 dequeuepeek 时会引发 panic:

let mut queue = Queue::new();

// 这里会 panic!
let x = queue.dequeue();

为了正确处理这种情况,当队列为空时,我们应该让 dequeuepeek 返回 None

impl Queue {

  // ...

  fn dequeue(&mut self) -> Option<i32> {
    if self.is_empty() {
        None
    } else {
        self.data.pop()
    }
  }

  fn peek(&self) -> Option<&i32> {
    self.data.first().copied()
  }

}

现在,当队列为空时,dequeuepeek 会安全地返回 None,而不会 panic。

实现有界队列(Bounded Queue)

上面的队列具有无界长度,因为我们使用 Vec 存储元素。我们可以通过实现一个具有固定容量的有界队列来改进这一点。

下面是一个使用固定大小数组作为存储的实现:

struct BoundedQueue {
    data: [i32; 5],
    head: usize,
    tail: usize,
    len: usize
}

impl BoundedQueue {

  fn new() -> Self {
     BoundedQueue { 
        data: [0; 5],
        head: 0,
        tail: 0,
        len: 0
     }
  }
  
  // ... enqueue, dequeue, 其他方法 ...
  
}

这里我们不再使用 Vec,而是使用一个包含 5 个元素的固定大小数组来存储队列项。

我们还需要跟踪 head(队首)和 tail(队尾)索引,以确定在数组中的哪个位置添加或移除元素。

这种有界队列的一些优点包括:

  • 固定的最大容量
  • 数组存储带来可预测的内存使用
  • 更容易分析时间和空间复杂度(Big O)

当然,固定容量意味着我们在入队时需要处理队列已满的情况。

我们可以返回一个 Result 而不是 panic:

enum Error {
  Full
}

impl BoundedQueue {

  // ...

  fn enqueue(&mut self, item: i32) -> Result<(), Error> {
    if self.len >= self.data.len() {
      return Err(Error::Full)
    }

    self.data[self.tail] = item;
    self.tail = (self.tail + 1) % self.data.len();
    self.len += 1;

    Ok(())
  }

}

这展示了有界队列如何为我们提供对队列行为的更多控制。

使用 Mutex 实现线程安全

到目前为止,我们的队列实现都不是线程安全的。多个线程同时访问可能会导致错误和状态不一致。

为了让队列线程安全,我们可以使用 Rust 的 Mutex 类型,它提供了互斥锁机制:

use std::sync::Mutex;

struct Queue {
  data: Mutex<Vec<i32>>
}

impl Queue {

  fn new() -> Self {
    Queue {
      data: Mutex::new(Vec::new())
    }
  }

  fn enqueue(&self, item: i32) {
    let mut data = self.data.lock().unwrap();
    data.push(item);
  }

  // ... 其他方法 ...

}

通过将内部的 Vec 包裹在 Mutex 中,我们确保同一时间只有一个线程可以访问队列。lock 方法返回一个锁守卫(lock guard),当它离开作用域时会自动释放锁。

这样就可以安全地在多个线程中并发地进行入队和出队操作。

阻塞队列实现

到目前为止的队列实现都是非阻塞的。enqueuedequeue 方法要么立即成功,要么返回一个错误结果。

有时我们希望队列在出队时如果为空则阻塞,或者在入队时如果已满则阻塞。这种阻塞队列在同步线程或进程时非常有用。

下面是一种使用 Rust 通道(channel)实现阻塞队列的方式:

use std::sync::{Arc, Mutex};
use std::sync::mpsc::{channel, Receiver, Sender};

struct BlockingQueue {
  sender: Mutex<Option<Sender<i32>>>,
  receiver: Mutex<Option<Receiver<i32>>>  
}

impl BlockingQueue {

  fn new() -> Self {
    let (sender, receiver) = channel();

    BlockingQueue {
      sender: Mutex::new(Some(sender)),
      receiver: Mutex::new(Some(receiver))
    }
  }

  fn enqueue(&self, item: i32) {
    let mut send = self.sender.lock().unwrap();
    send.as_ref().unwrap().send(item).unwrap();
  }

  fn dequeue(&self) -> i32 {
    let mut recv = self.receiver.lock().unwrap();
    recv.as_mut().unwrap().recv().unwrap()
  }

}

通过使用 Rust 的通道作为内部队列存储,sendrecv 操作会在通道满或空时自动阻塞。

这样就无需自己管理阻塞逻辑,即可获得阻塞队列的语义。