yocto-queue源码解读:链表、迭代器与高效队列

72 阅读3分钟

引言

数据结构的选择可以大大影响代码的性能和效率。今天,我们通过解读 yocto-queue 这个仅 67 行代码实现高效队列的开源库,看下使用链表、和迭代器如何实现一个更高效的队列

源码

class Node {
  value;
  next;

  constructor(value) {
   this.value = value;
  }
}

export default class Queue {
  #head;
  #tail;
  #size;

  constructor() {
   this.clear();
  }

  enqueue(value) {
   const node = new Node(value);

   if (this.#head) {
    this.#tail.next = node;
    this.#tail = node;
   } else {
    this.#head = node;
    this.#tail = node;
   }

   this.#size++;
  }

  dequeue() {
   const current = this.#head;
   if (!current) {
    return;
   }

   this.#head = this.#head.next;
   this.#size--;
   return current.value;
  }

  clear() {
   this.#head = undefined;
   this.#tail = undefined;
   this.#size = 0;
  }

  get size() {
   return this.#size;
  }

  * [Symbol.iterator]() {
   let current = this.#head;

   while (current) {
    yield current.value;
    current = current.next;
   }
  }
}

队列与链表实现

队列,在数据结构中,是一个先进先出(FIFO) 的结构。在 JS 中,我们可以使用数组来实现队列,但在某些情况下,链表提供了更高的效率。特别是当你需要在数据结构的末端插入和删除数据时,链表的时间复杂度是常数O(1),而数组则需要线性时间O(n)。我们的实例代码就采用了这样的有向链表实现队列,采用私有变量(#head 和 #tail)追踪队列的头部和尾部。

enqueue 这个方法中,yocto-queue通过直接给 this.#tail.next 的方式赋值,实现高效的添加数据,如果我们没有追踪对尾部的引用,那么当我们需要在队列尾部添加一个新元素时,我们需要遍历整个链表以找到最后一个节点 - 这是一个 O(n) 的操作。通过维护 #tail 的引用,现在我们可以在 O(1) 时间内执行这个操作,显著提高了性能。

迭代器

迭代器允许我们使用 for...of 循环直接遍历队列的元素。这在底层实现上无需向外部暴露队列的内部结构即可使整个队列可迭代,极大地提高了代码的可读性和易用性。

Symbol.iterator 方法允许对象(在这个案例中,队列)在被迭代(例如在 for...of 循环中)时创建并返回其值的一个流。

在yocto-queue的迭代器方法中,做了以下操作:

  1. let current = this.#head,将当前节点初始化为队列头部(第一个节点)。
  2. 之后,进入一个 while 循环,条件是 current 必须存在。这意味着只要还有节点,循环就会继续。
  3. 在循环中,使用 yield 语句返回当前节点的值。这使得每次在迭代过程中请求的下一个值都将是队列中的下一个节点值。
  4. current = current.next,将当前节点移到下一个节点,跨过整个队列。当没有更多节点(即到达队列尾部)时,current 将变为 null 或 undefined ,循环结束。

使用示例

import Queue from './queue';

// 创建队列,在超市买东西要排队结账
const supermarketQueue = new Queue();

// 开始排队
supermarketQueue.enqueue("Person 1");
supermarketQueue.enqueue("Person 2");
supermarketQueue.enqueue("Person 3");

console.log("队列的大小:", supermarketQueue.size); // 输出:3

// 第一个人服务完毕,离开队列 - dequeuing
const servedPerson = supermarketQueue.dequeue();

console.log("Served person: ", servedPerson); // 输出:Person 1
console.log("队列的大小:", supermarketQueue.size); // 输出:2

// 遍历队列里的人
for (const person of supermarketQueue) {
  console.log(person);
}
// 输出:
// Person 2
// Person 3

总结

链表提供了一种高效的方式来实现队列。同时,JS 内置迭代器机制为我们提供了一个方便、简洁的方式来遍历链式队列。选择正确的数据结构以及有效使用内置的 JavaScript 机制是构建高性能,高效应用的关键步骤。

相关资料

Symbol.iterator

Iterator

yocto-queue