读一读源码
今天选了一个github上的库,yocto-queue。如果你有大量的操作数组需求,并且只是为数组末尾添加元素,或在数组头部删除元素,就可以用这个库。
这个库将以上的操作,转换成队列的思想,这样你在数组头部添加或删除元素的时候,这个操作的时间复杂度由O(n)变成O(1)。
原因是数组在计算机上是连续存储的,数组是物理。而列队不是,列队是逻辑。在列队头部添加/删除元素,只要操对头的元素的指向就好了,时间复杂度远远比数组要低。
接下来我们看看yocto-queue上的介绍:
yocto-queue
Tiny queue data structure
You should use this package instead of an array if you do a lot of Array#push() and Array#shift() on large arrays, since Array#shift() has linear time complexity O(n) while Queue#dequeue() has constant time complexity O(1) . That makes a huge difference for large arrays.
A queue is an ordered list of elements where an element is inserted at the end of the queue and is removed from the front of the queue. A queue works based on the first-in, first-out (FIFO) principle.
以下是用法:
import Queue from 'yocto-queue';
const queue = new Queue();
queue.enqueue('🦄');
queue.enqueue('🌈');
console.log(queue.size);
//=> 2
console.log(...queue);
//=> '🦄 🌈'
console.log(queue.dequeue());
//=> '🦄'
console.log(queue.dequeue());
//=> '🌈'
看一眼仓库目录
可以看到这个库真的很小,主要代码是index.js
进去看看代码:
/*
How it works:
`this.#head` is an instance of `Node` which keeps track of its current value and nests another instance of `Node` that keeps the value that comes after it. When a value is provided to `.enqueue()`, the code needs to iterate through `this.#head`, going deeper and deeper to find the last value. However, iterating through every single item is slow. This problem is solved by saving a reference to the last value as `this.#tail` so that it can reference it to add a new value.
*/
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;
}
}
}
enqueue和dequeue就是重点中的重点。
先看enqueue(入队),主要的思想是,判断列队中有没有 #head(就是队列头),没有的话就将队列的头(#head)和尾(#tail)都指向这个元素。而如果有 #head,就从#tail入队,不能从#head入队噢。然后在计数器#size++,为的是记录队列的长度。
再看dequeue(出队),出队非常简单,只要把#head指向#head的next就可以了,然后计数器#size--。
这时,有人问,那没有指向的那些元素去哪了,答案是被V8的GC回收了。
然后到clear,清除整个队列,只要把#head、#tail、#size全部置空就可以了。