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');
// 双工 能读能写