上一节讲了可读流,在讲可写流之前得了解一下链表。
比如:并发往文件中写入数据
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 });