yocto-queue 源码

640 阅读2分钟

读一读源码

今天选了一个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.

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());
//=> '🌈'



看一眼仓库目录 企业微信截图_16624583551062.png 可以看到这个库真的很小,主要代码是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全部置空就可以了。