上一节讲了可读流,在讲可写流之前得了解一下链表。
比如:并发往文件中写入数据
write("1");
write("2");
write("3");
每次写都会开个线程,上面的写入可能出现 123
,321
,213
…
node 中主线程是单线程,没有锁的概念。
怎么解决这种问题?
线性的数据结构
队列:比如宏任务微任务就是队列,从第一个开始取出来执行,先进先出
- 入队:
arr.push
- 出队:
arr.shift
栈结构:代码执行会销毁的顺序,就是一个栈型结构,先进后出
- 入栈:
arr.push
- 出栈:
arr.pop
链表:链表从头和尾取值性能高,也能存放数据,平均查询的复杂度是 O(n)
,基于链表可以实现队列的封装,没有数组的塌陷问题。
- 单向链表:每个节点有 next 属性指向下一个,头指向第一个元素
- 单向循环链表:最后一个元素指向头部
- 双向链表:两个指针分别指向前一个和后一个
- 双向循环链表:两个指针分别指向前一个和后一个,第一个的前一个指向尾,最后的后一个指向头
- 环形链表:最后一个元素的 next 指向了其他的节点
简单的实现单向链表
class Node {
constructor(element, next) {
this.element = element;
this.next = next;
}
}
class LinkedList {
constructor() {
this.head = null; // 链表的头
this.size = 0; // 链表长度
}
// 可以直接在尾部添加内容,或者根据索引添加
add(index, element) {
// 传入一个参数是需要设置一下 index, element
if (arguments.length === 1) {
// 在尾部添加,传入的 index 就当做是 element
element = index;
// 然后把 this.size 当做索引
index = this.size;
}
// 处理越界可能
if (index < 0 || index > this.size) throw new Error("越界");
// 判断 index 是否为 0
if (index === 0) {
// 老的头
let head = this.head;
// 设置新头,将老的头变为当前节点的下一个
this.head = new Node(element, head);
} else {
// 先找到当前索引的上一个
let prevNode = this.getNode(index - 1);
// 将当前上一个节点的 next 指向新的节点,新的节点的下一个指向上一个节点的 next
prevNode.next = new Node(element, prevNode.next);
}
// 累加 size
this.size++;
}
getNode(index) {
// 从头开始找
let current = this.head;
// 不能向后找,找到索引的位置
for (let i = 0; i < index; i++) {
current = current.next;
}
return current;
}
}
let ll = new LinkedList();
ll.add(0, 1);
ll.add(0, 2);
ll.add(3);
ll.add(1, 4);
console.dir(ll, { depth: 100 });