55 # 实现可写流

先在 LinkedList.js 给链表添加一个移除方法

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;
    }
    remove(index) {
        if (index === 0) {
            let node = this.head;
            if (!node) return null;
            this.head = node.next;
            this.size--;
            return node.element;
        }
    }
}

let ll = new LinkedList();
ll.add(0, 1);
ll.add(0, 2);
ll.add(3);
ll.add(1, 4);

console.dir(ll, { depth: 100 });
console.dir(ll.remove(0));
console.dir(ll, { depth: 100 });

module.exports = LinkedList;

在这里插入图片描述

下面实现可写流:

  1. 先创建一个队列的类,利用上面 LinkedList 维护一个链表
  2. 然后创建自己的可写流 KaimoWriteStream 类继承 EventEmitter
  3. 再区分是否是在写入状态,根据写入状态确定存缓存还是真正的写入
  4. 最后写入完一个之后,判断是否需要清空缓存,需要的话就继续将 poll 返回的数据继续写入
const EventEmitter = require("events");
const fs = require("fs");
let LinkedList = require("./LinkedList");

class Queue {
    constructor() {
        this.LinkedList = new LinkedList();
    }
    offer(element) {
        this.LinkedList.add(element);
    }
    poll() {
        return this.LinkedList.remove(0);
    }
}

class KaimoWriteStream extends EventEmitter {
    constructor(path, opts = {}) {
        super();
        this.path = path;
        this.flags = opts.flags || "w";
        this.autoClose = opts.autoClose || true;
        this.encoding = opts.encoding || "utf8";
        this.start = opts.start || 0;
        this.mode = opts.mode || 0o666;
        this.highWaterMark = opts.highWaterMark || 16 * 1024;

        // 维护当前存入的数据个数
        // 每次调用 write 方法,会根据写入的内容的个数累加给 len 属性(缓存的长度)
        this.len = 0;
        // 是否正在写入
        this.writing = false;
        // 是否需要触发 drain 事件
        this.needDrain = false;
        // 写入的偏移量
        this.offset = this.start;
        // 用来缓存的队列
        this.cache = new Queue();
        // 默认先打开文件
        this.open();
    }
    // open 方法是异步的
    open() {
        fs.open(this.path, this.flags, this.mode, (err, fd) => {
            if (err) {
                return this.emit("error", err);
            }
            // 将 fd 保存到实例上,用于稍后的读取操作
            this.fd = fd;
            this.emit("open", fd);
        });
    }
    write(chunk, encoding = "utf8", cb = () => {}) {
        // 统一转为 buffer
        chunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
        this.len += chunk.length;
        // write 方法的返回值
        let flag = this.len < this.highWaterMark;
        // drain 事件的触发:1.必须写入的个数达到预期或者超过预期
        this.needDrain = !flag;
        if (this.writing) {
            // 正在写入
            this.cache.offer({
                chunk,
                encoding,
                cb
            });
        } else {
            // 没有正在写入
            this.writing = true; // 标识正在写入了
            // 真正写入的逻辑
            this._write(chunk, encoding, () => {
                // 原来用户传入的 callback
                cb();
                // 当前内容写入完毕后清空缓存区中的内容
                this.clearBuffer();
            });
        }
        return flag;
    }
    _write(chunk, encoding, cb) {
        // 写入必须要等待文件打开完毕,如果打开了会触发 open 事件
        if (typeof this.fd !== "number") {
            // 如果没有 fd 就返回一个 open 的一次性事件,再去回调 _write 方法
            return this.once("open", () => this._write(chunk, encoding, cb));
        }
        // 将用户数据写入到文件中
        fs.write(this.fd, chunk, 0, chunk.length, this.offset, (err, written) => {
            if (err) {
                return this.emit("error", err);
            }
            this.len -= written; // 缓存中的数量要减少
            this.offset += written;
            console.log("chunk--->", chunk.toString());
            cb(); // 当前文件内容写入完毕后,再去清空缓存中的
        });
    }
    clearBuffer() {
        let data = this.cache.poll();
        if (data) {
            // 需要清空缓存
            let { chunk, encoding, cb } = data;
            this._write(chunk, encoding, () => {
                cb();
                // 当前缓存的第一个执行后,再去清空第二个
                this.clearBuffer();
            });
        } else {
            this.writing = false;
            if (this.needDrain) {
                // 当前触发后下次就不需要再次触发了
                this.needDrain = false;
                this.emit("drain");
            }
        }
    }
}

module.exports = KaimoWriteStream;

下面用实现的可写流测试一下上一节的例子:写入10个数,只占用一个字节的内存

const path = require("path");

const KaimoWriteStream = require("./55/KaimoWriteStream");

let ws = new KaimoWriteStream(path.resolve(__dirname, "./55/number.txt"), {
    highWaterMark: 3 // 利用 highWaterMark 来控制写入的速率
});

let numberIndex = 0;
function write() {
    let flag = true; // 是否可以写入
    while (flag && numberIndex < 10) {
        flag = ws.write(numberIndex + "");
        numberIndex++;
    }
}
write();
ws.on("drain", () => {
    console.log("ws---drain--->");
    write();
});

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/42451.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java贪心算法案例

1.零钱找回问题 这个问题在我们的日常生活中就更加普遍了。假设1元、2元、5元、10元、20元、50元、100元的纸币分别有c0, c1, c2, c3, c4, c5, c6张。现在要用这些钱来支付K元&#xff0c;至少要用多少张纸币&#xff1f;用贪心算法的思想&#xff0c;很显然&#xff0c;每一步…

计算机网络 day7 扫描IP脚本 - 路由器 - ping某网址的过程

目录 network 和 NetworkManager关系&#xff1a; 实验&#xff1a;编写一个扫描脚本&#xff0c;知道本局域网里哪些ip在使用&#xff0c;哪些没有使用&#xff1f; 使用的ip对应的mac地址都要显示出来 计算机程序执行的两种不同方式&#xff1a; shell语言编写扫描脚本 …

漏洞攻击 --- TCP -- 半开攻击、RST攻击

TCP半开攻击&#xff08;半连接攻击&#xff09; --- syn攻击 &#xff08;1&#xff09;定义&#xff1a; sys 攻击数据是DOS攻击的一种&#xff0c;利用TCP协议缺陷&#xff0c;发送大量的半连接请求&#xff0c;耗费CPU和内存资源&#xff0c;发生在TCP三次握手中。 A向B…

为什么ConcurrentHashMap不允许插入null值而HashMap可以?

为什么ConcurrentHashMap不允许插入null值而HashMap可以&#xff1f; 文章目录 为什么ConcurrentHashMap不允许插入null值而HashMap可以&#xff1f;HashMap源码ConcurrentHashMap源码为什么ConcurrentHashMap需要加空值校验呢&#xff1f;二义性问题测试代码代码分析测试结果结…

LangChain + Embedding + Chromdb,关联使用ChatGLM的本地搭建训练平台教程

一.介绍 OpenAI 在国内用户注册会遇到各种阻力&#xff0c;目前可行的方法是使用本地数据集的功能实现联网搜索并给出回答&#xff0c;提炼出TXT、WORD 文档里的内容。 现在主流的技术是基于强大的第三方开源库&#xff1a;LangChain 。 文档地址&#xff1a;&#x1f99c;…

win11安装redis步骤详解

文章目录 一、redis的安装与下载1、下载2、解压3、启动redis4、测试是否安装成功 二、将redis加入到windows的服务中三、常用的redis服务命令 安装可参考的资料&#xff1a;https://www.runoob.com/redis/redis-install.html 一、redis的安装与下载 1、下载 下载地址&#xf…

提示工程师:如何写好Prompt

提示工程由来 提示工程是一门相对较新的学科&#xff0c;用于开发和优化提示以有效地将语言模型 (LM) 用于各种应用程序和研究主题。 研究人员使用提示工程来提高 LLM 在广泛的常见和复杂任务&#xff08;例如问题回答和算术推理&#xff09;上的能力。 开发人员使用提示工程…

120、仿真-51单片机温湿度光照强度C02 LCD1602 报警设计(Proteus仿真+程序+元器件清单等)

方案选择 单片机的选择 方案一&#xff1a;STM32系列单片机控制&#xff0c;该型号单片机为LQFP44封装&#xff0c;内部资源足够用于本次设计。STM32F103系列芯片最高工作频率可达72MHZ&#xff0c;在存储器的01等等待周期仿真时可达到1.25Mip/MHZ(Dhrystone2.1)。内部128k字节…

【Docker】Docker安装与操作

docker的安装与命令 一、安装 docker1. 安装依赖包2. 信息查看 二、Docker 镜像操作1. 搜索镜像2. 获取镜像3. 镜像加速下载4. 查看镜像相关信息5. 删除镜像6. 上传镜像7. 存出和载入镜像 三、Docker 容器操作1. 创建容器2. 查看容器3. 启动容器4. 停止容器5. 进入容器6. 容器与…

SpringBoot整合SpringCloudStream3.1+版本Kafka

SpringBoot整合SpringCloudStream3.1版本Kafka 下一节直通车 SpringBoot整合SpringCloudStream3.1版本的Kafka死信队列 为什么用SpringCloudStream3.1 Springcloud架构提供&#xff0c;基于spring生态能够快速切换市面上常见的MQ产品3.1后使用配置文件的形式定义channel&am…

# Linux下替换删除文件中的颜色等控制字符的方法

Linux下替换删除文件中的颜色等控制字符的方法 文章目录 Linux下替换删除文件中的颜色等控制字符的方法1 Linux下的控制字符&#xff08;显示的文字并不是他本身&#xff09;&#xff1a;2 颜色字符范例&#xff1a;3 替换4 最后 我们在shell编程显示输出时&#xff0c;会定义文…

Linux的时间函数

2023年7月19日&#xff0c;周三下午 我今天基于GitHub搭建了自己的博客网站&#xff0c;欢迎大家来我的个人博客网站阅读我的博客 巨龙之路的GitHub个人博客 (julongzhilu.github.io) 目录 time函数原型使用方法ctime函数原型使用方法疑惑gmtime、 localtime函数原型什么是分…

WEB:FlatScience

背景知识 sql注入 SQLite数据库知识 SQLite3注入方法 题目 用dirsearch进行扫描&#xff0c;下面几个关键目录&#xff1a;robots.txt&#xff0c;login.php&#xff0c;admin.php&#xff0c;剩下的目录就是一些pdf格式的论文了 一个一个访问并查看源代码&#xff0c;在查看l…

常用API学习06(Java)

Biglnteger public BigInteger(int num, Random rnd) 获取随机大整数&#xff0c;范围&#xff1a;[0~2的num次方-1] public BigInteger(String val) 获取指定的大整数 public BigInteger(String val, int radix) 获取指定进制的大整数 public static BigInteg…

怎么给pdf文件加密?pdf文档如何加密

在数字化时代&#xff0c;保护个人和机密信息的重要性越来越受到关注。PDF&#xff08;Portable Document Format&#xff09;是一种广泛使用的文件格式&#xff0c;用于共享和存储各种类型的文档。然而&#xff0c;由于其易于编辑和复制的特性&#xff0c;保护PDF文件中的敏感…

一张表实现短视频“评论区“完整功能

前言 现如今&#xff0c;不管是哪种类型的应用&#xff0c;评论区都少不了。从工具类的到媒体信息流类的&#xff0c;评论留言都是最基本的互动环节。比如抖音短视频下&#xff0c;针对视频每个用户都可以发表自己的观点&#xff1b;而针对用户的评论&#xff0c;其他的用户又可…

Spring Cloud—GateWay之限流

RequestRateLimiter RequestRateLimiter GatewayFilter 工厂使用 RateLimiter 实现来确定是否允许当前请求继续进行。如果不允许&#xff0c;就会返回 HTTP 429 - Too Many Requests&#xff08;默认&#xff09;的状态。 这个过滤器需要一个可选的 keyResolver 参数和特定于…

Spring @RequestMapping 工作原理

Spring RequestMapping 工作原理 配置基础启动类及Controller类 SpringBootApplication public class DemoServiceApplication {public static void main(String[] args) {SpringApplication.run(DemoServiceApplication.class, args);} }RestController public class HelloC…

单元测试用例到底该如何设计?

目录 前言 使用参数方法创建测试用例 使用执行路径方法创建测试用例 总结 前言 最近一些大公司在进行去测试化的操作&#xff0c;这一切的根源大概可以从几年前微软一刀切砍掉所有内部正式的测试人员开始说起&#xff0c;当时微软内部的测试工程师有一部分转职成了开发工程…

【数据结构常见七大排序(三)上】—交换排序篇【冒泡排序】And【快速排序】

目录 前言 1.冒泡排序 1.1冒泡排序动图 1.2冒泡排序源代码 1.3冒泡排序的特性总结 2.快速排序&#x1f451; 2.1hoare版本实现思想 排序前 排序中 排序后 2.2hoare版本快排源代码 2.3分析先走 情况1&#x1f947; 情况2&#x1f948; 前言 交换类排序两个常见的排…