Bootstrap

Nodejs之buffer

Buffer是用来存放内容的 (标识的是内存空间)

  • buffer声明方式需要指定大小

  • 长度 指定buffer中存放的特定内容 我们可以直接给字符串

    console.log(Buffer.alloc(3)); // node中的最小单位都是字节
    console.log(Buffer.from([100, 200])); // 这种方式不常用
    console.log(Buffer.from('帅'))
    
  • 内存一但申请了,无法直接在原内存上进行扩展

  • 在前端上传文件的时候(分片上传) 创建一个大的内存,来存储多段数据

  • 合并数据(tcp分段传输的,我们肯定希望拿到数据后可以进行拼接操作

Buffer方法

  • Buffer.alloc() 分配大小
  • Buffer.from() 将内容转换成buffer
  • Buffer.copy()
  • Buffer.concat() 拼接
  • buffer.slice() 截取内存 浅拷贝 截取的是内存空间,修改会影响原buffer
  • toString() 转换成字符串
  • buffer.length 是字节长度
  • Buffer.isBuffer() 在node中处理数据 有字符串和buffer共存的情况,为了保证不出错,我们一般全部转换成buffer来处理
  • buffer.split() 基于之前的方法封装

实现原型继承有几种方案

  • obj.prototype.proto = parent.prototype
  • obj.prototype = Object.create(parent.prototype)
  • Object.setPrototypeOf( obj.prototype,parent.prototype)

文件读写

文件的读写 如果相对于内存来说的话 就是反过来的 读取文件其实是像内存中写入 , 要是写入内容,将数据读取出来

内存的空间是有限的,数据过大,都写入到内存(淹没可用内存) 64k以下的文件可以采用readFile

  • 如果大文件 不能采用readFile来读取

  • node中提供了手动读取的方式 (api)

// 1)  打开   2)指定读取的位置  3) 关闭文件

// flags 
// r: 我打开文件的目的是读取文件
// w; 我打开文件的目的是写入
// a: 我们打开文件是追加内容的append方法  
// r+: 如果文件不存在会报错, 具备读取和写入的能
// w+: 如果文件不存在会创建, 具备读取和写入的能

// 权限位置 8禁止
// 二进制的组合 : 权限的组合  1用户的执行操作  2用户的写入操作  4用户的读取操作   位运算
// chmod -R 777
// 001 | 010  按位或可以将权限组合起来  011

// 001 
// 010
// 100
// 111

// 001  
// 010
// 100
// 1000 

// 按照位来做组合 ,可以将权限组合起来

可读流

const fs = require('fs');
const EventEmitter = require('events')
class ReadStream extends EventEmitter{ 
    constructor(path, options) {
        super()
        this.path = path;
        this.flags = options.flags || 'r';
        this.encoding = options.encoding || null;
        this.mode = options.mode || 0o666;
        this.autoClose = options.autoClose || true;
        this.emitClose = options.emitClose || true;
        this.start = options.start || 0;
        this.end = options.end;
        this.pos = this.start
        this.flowing = false; // 非流动模式
        this.highWaterMark = options.highWaterMark || 64 * 1024
        // 这里是同步监听,用户绑定了data会立刻出发这个回调
        this.on('newListener',  (type) =>{
            if (type === 'data') {
                this.flowing = true;
                this.read();
            }
        })
        this.open()
    }
    destroy(err) {
        if (this.fd) {
            fs.close(this.fd,()=>this.emit('close'))
        }
        if (err) {
            this.emit('error',err)
        } 
    }
    open() {
        fs.open(this.path, this.flags, this.mode,  (err,fd) => {
            if (err) this.destroy(err);
            this.fd = fd;
            this.emit('open',fd)
        })
    }
    pause() {
        this.flowing = false
    }
    resume() {
        if (!this.flowing) {
            this.flowing = true;
            this.read()
        }
    }
    read() {
        if (typeof this.fd !== 'number') {
            return this.once('open',()=>this.read())
        }
        // 文件已经打开可以读取了
        const buffer = Buffer.alloc(this.highWaterMark)

        const howMuchToRead = this.end ?  
        Math.min(this.end - this.pos + 1 ,  this.highWaterMark)
        : this.highWaterMark

        fs.read(this.fd, buffer, 0, howMuchToRead, this.pos, (err, bytesRead) => {
            if(err) return this.destroy()
            if (bytesRead == 0) {
                this.emit('end')
                this.destroy();
            } else {
                this.pos += bytesRead
                this.emit('data', buffer.slice(0,bytesRead))
                if (this.flowing) {
                    this.read()
                }
            }
           
            
        })
    }
}

module.exports = ReadStream
const fs = require('fs');
const path = require('path')
// const rs = fs.createReadStream(path.resolve(__dirname, 'test.md'), {
const ReadStream = require('./ReadStream')
const rs = new ReadStream(path.resolve(__dirname, 'test.md'), {
    // fs.open(this.emit('open'))  fs.read(this.emit('data'))  fs.close
    flags: 'r', // fs.open(flags)
    encoding: null, // 标识读取的编码就是buffer格式
    mode: 0o666,
    autoClose: true, // 关闭文件 fs.close
    emitClose: true, // 触发关闭事件 this.emit('close')
    start: 0,
    end: 4, // 我要读取索引从0开始到索引为5的位置
    highWaterMark: 2 // 控制读取的速率,字节单位  默认64k
});
rs.on('open', function (fd) {
    console.log(fd)
})
const arr = [];
rs.on('data', function (chunk) { // 可以监听data事件会让非流动模式变为流动模式
    arr.push(chunk);
    console.log(chunk)
    rs.pause(); // 暂停读取操作,这个时候可能我要消费读取到的数据
})
rs.on('end', function () { // 可以监听data事件会让非流动模式变为流动模式
    console.log(Buffer.concat(arr).toString())
})
rs.on('close', function () {
    console.log('close')
})
rs.on('error', function (err) {
    console.log('error')
})

setInterval(() => {
    rs.resume()
}, 1000);

// open 和 close 是针对文件来说的,这两个方法不输于可独流
// 可独流都拥有 on('data')  on('end') 就是一个可独流  pause()  resume()

可写流

const fs = require('fs');
const EventEmitter = require('events')
class WriteStream extends EventEmitter {
    constructor(path, options) {
        super();
        this.path = path;
        this.flags = options.flags || 'w';
        this.autoClose = options.autoClose || true;
        this.emitClose = options.emitClose || true;
        this.start = options.start || 0;
        this.highWaterMark = options.highWaterMark || 16 * 1024

        this.cache = []; // 这里除了第一次写入之后,都会将写入操作放置到队列中
        this.writing = false; // 默认情况 没有调用过write
        this.needDrain = false; // 只有当写入的个数,达到了highwaterMark并且被清空后才会从触发drain事件
        this.len = 0; // 默认用于记录写入的个数

        this.offset = this.start; // 记录写入的位置

        this.open()
    }
    destroy(err) {
        if (err) {
            return this.emit('error', err)
        }
    }
    open() {
        fs.open(this.path, this.flags, (err, fd) => {
            if (err) return this.destroy(err);
            this.fd = fd;
            this.emit('open', fd)
        })
    }
    clearBuffer() {
        let cacheObj = this.cache.shift(); // 获取缓存的头部
        if (cacheObj) {
            this._write(cacheObj.chunk,cacheObj.encoding,cacheObj.callback)
        } else {
            this.writing = false;
            if (this.needDrain) {
                this.needDrain = false;
                this.emit('drain'); // 等待内存中没有值了,则触发drain事件
            }
        }
    }
    close() {
        fs.close(this.fd,()=>this.emit('close'))
    }
    end(chunk, encoding, callback = () => { }) {
        this.write(chunk, encoding, () => {
            this.needDrain = false; // 最后一次不要怕触发drain事件
            callback();// 关闭文件
            this.close()
        })
    }
    // 此write方法是用户调用的,并不是真正的写入方法
    write(chunk, encoding = 'utf8', callback = () => { }) {
        chunk = Buffer.isBuffer(chunk) ? chunk: Buffer.from(chunk)
        this.len += chunk.length
        // 写入的个数超过预期了,不要在给我了
        let res = this.len >= this.highWaterMark
        this.needDrain = res; // 只有达到预期最后都写入完毕后,看此值是否为true,触发drain事件
        const fn = () => {
            callback();
            this.clearBuffer(); // 清空缓存区的内容
        }
        if (!this.writing) {
            // 需要将本次内容写入到文件中 
            this.writing = true;
            
            this._write(chunk, encoding, fn); // 真的像文件中写入
        } else {
            // 放到队列中(内存)
            this.cache.push({
                chunk,
                encoding,
                callback:fn
           })
        }
        return !res;
      
    }
    _write(chunk,encoding,callback) {
        if (typeof this.fd !== 'number') {
        return this.once('open',()=>this._write(chunk,encoding,callback))
        }
        fs.write(this.fd, chunk, 0, chunk.length, this.offset,  (err,written) => {
            this.offset += written; // 写入后增加偏移量
            this.len -= written 
            callback();
        })
    }
}

module.exports = WriteStream
const fs = require('fs');
const path = require('path');
const WriteStream = require('./WriteStream')
const ws = new WriteStream(path.resolve(__dirname, 'copy.md'), {
// const ws = fs.createWriteStream(path.resolve(__dirname, 'copy.md'), {
  flags: 'w',
  mode: 0o666,
  autoClose: true,
  emitClose: true,
  start: 0,
  highWaterMark: 3
});
let idx = 0;
function write() {
    if (idx < 10) {
        let flag = ws.write(idx++ + '');
        console.log(flag);
        if (flag) {
            write();
        }
    } else {
        // end - write + close
        ws.end('结束');
    }
}
write();
ws.on('open', function (fd) {
    console.log(fd)
})
ws.on('drain', function () {
  console.log('抽干');
  write();
});

// on('data') on('end')
// write()  end()

// rs.pipe(ws)

stream

const { Readable, Writable, Duplex, Transform } = require('stream')

// node中可以实现进程间通信,通信就可以通过流来通信 (进程通信就通过这三个方法)

process.stdin // 标准输入,用户输入的内容会触发此方法
process.stdout
process.stderr


// process.stdin.on('data', function (chunk) { // 读取用户的输入
//     process.stderr.write(chunk); // 标准输出 底层和console一个方法
// })

// class MyTransfrom extends Transform{
//     _transform(chunk,encoding,callback) { // 参数是可写流的参数
//         // 将处理后的结果 可以使用push方法传给可读流
//         this.push(chunk.toString().toUpperCase())
//         callback()
//     }
// }
// const transform = new MyTransfrom
// process.stdin.pipe(transform).pipe(process.stderr)

const zlib = require('zlib');
const fs = require('fs');  // gzip压缩 将重复的内容尽可能的替换,重复率越高压缩效果越好

// fs.readFile
// fs.createReadstram
// const rs = fs.createReadStream('./1.txt')
// const ws = fs.createWriteStream('./1.txt.gz')
// rs.pipe(zlib.createGzip()).pipe(ws)


const crypto = require('crypto');
const rs = fs.createReadStream('./1.txt')
const ws = fs.createWriteStream('./2.txt')
// 加密 和 摘要不一样 (加密表示可逆,摘要是不可逆的)
// 典型的摘要算法 md5 

// 1.md5 相同的内容摘要的结果一定是相同的
// 2.如果内容有差异结果会发生 很大变化,雪崩效应
// 3.无法反推
// 4.就是不同的内容摘要的长度相同

// console.log(crypto.createHash('md5').update('abc').update('d').update('e1').digest('base64'))
const md5 = crypto.createHash('md5');
md5.setEncoding('base64')
rs.pipe(md5).pipe(ws); // 做转化的


// 能读也能写 内部需要实现 _read 和 _write

// class MyDuplex extends Duplex{
//     constructor() {
//         super()
//         this.data = ''
//     }
//     _read() {
//         // 读取走这里
//         console.log('读取');
//         this.push(this.data)
//         this.push(null); // 没有放入数据 所以没有触发dta
//     }
//     _write(chunk) {
//         // 写入走这里
//         this.data = chunk
//     }
// }
// const myDuplex = new MyDuplex
// myDuplex.on('data', function (chunk) {
//     console.log(chunk)
// })
// myDuplex.on('end', function () {
//     console.log('end')
// })
// myDuplex.write('ok');

// 双工 能读能写


;