node笔记

node是v8引擎,单线程(指的是主线程)非阻塞IO,轻量高效。

优势:

  • 1.节约内存,因为只有一个线程,但是崩了就挂了。
  • 2.节约上下文切换的时间
  • 3.解决锁的问题,并发资源的处理。

多线程是如何实现的?

  • 是通过非常快速的切换时间片来实现的

webworker多线程:是完全手主线程控制的,不能操作dom。

eventloop:栈从上到下执行(要执行完),然后过程可能有函数发请求或者定时器等异步请求,开辟新的线程,追加到回调队列里,按照先后顺序执行。

function read(){
console.log('1');
setTimeout(function(){
console.log('2');
  setTimeout(function(){
     console.log('4');
  })
})
setTimeout(function(){
console.log('5');
})
console.log('3')
}
read();
//1,3,2,5,4

同步异步

同步时发出调用之后,没有得到结果之前,调用不会返回值,会一直查找,一旦找到就返回,可以理解为调用者主动等待这个调用的结果
异步时在调用者发出调用后这个调用就直接返回了,所以没有返回结果,当一个异步过程调用发出后,调用者不会立刻得到结果,而时调用发出后,被调用者通过状态同值或者回调函数处理这个调用

阻塞与非阻塞

阻塞与非阻塞关注的时程序在等待调用结果(消息或者返回值)状态
阻塞:调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回,
非阻塞:指不能立刻得到结果之前该调用不会阻塞当前进程

同步异步取决于被调用者,阻塞非阻塞取决于调用者

Node的REPL

R:读
E:eval计算
P:写
L:loop

console

标准流 1

  • console.log()
  • console.info()
    错误流2
  • console.warn()
  • console.error()
    测试时间
    console.time('a')
    console.timeEnd('a')
    //TDD测试驱动开发、BDD行为驱动开发,单元测试
    断言
    console.assert()
    如果表达式表达为真就不输出,为假就报错,用于监控
function sun(a,b){
return a+b
}
console.assert(sum(1,2)==3,'报错')

console.dir()
//可以列出对象的的结构
console.trace();
//可以跟踪当前代码的调用栈
//程序执行从上往下执行,栈是先进后出

global全局对象

window里也有全局对象,但是不能直接访问,我们在浏览器里访问global是通过window实现的
1.global的变量是全局变量
2.所有的全局变量都是global的属性
console.log(global)
//chdir:改变目录
//cwd:当前工作目录

  • process当前进程
    process.cwd()
    process.chdir()
    nextTick 把回调函数放在当前执行栈的底部
    setImmediate把回调函数放在事件队列的尾部

event

events.js

function EventEmitter() {
  this.events = {};//所有时间监听函数放在对象里保存
  this._maxListeners = 10;//指定一个事件类型增加的监听函数数量最多个数
  //给指定的事件绑定事件处理函数,1参数是事件类型,2是事件监听函数
  EventEmitter.prototype.setMaxListeners = function (maxListeners) {
    this._maxListeners = maxListeners
  }
  EventEmitter.prototype.listeners = function (event) {
    return this.events[event]
  }
  EventEmitter.prototype.on = EventEmitter.prototype.addListenter = function (type, listener) {
    if (this.events[type]) {
      this.events[type].push(listener);
      if (this._maxListeners != 0 && this.events[type].length > this._maxListeners) {
        console.error(`error${type}`)
      }
    } else {
      this.events[type] = [listener]
    }
    EventEmitter.prototype.once = function (type, listener) {
      let wrapper = (...rest) => {
        listener.apply(this, rest);
        this.removeListener(type, wrapper);
      }
      this.on(type, wrapper);
    }
    EventEmitter.prototype.removeListener = function (type, listener) {
      if (this.events[type]) {
        this.events[type] = this.events[type].filter(lis => lis !== listener)
      }
    }
    EventEmitter.prototype.removeAllListeners = function (type) {
      delete this.events[type]
    }
    EventEmitter.prototype.emit = function (type, ...rest) {
      this.events[type] && this.events[type].forEach(listener => listener.apply(this, rest))
    }
  }
}
module.exports = EventEmitter;

test.js

let EventEmitter = require('./events');
// let EventEmitter = require('events');
let util = require('util');
// console.log('util: ', util);
function Bell() {
  EventEmitter.call(this);//继承私有属性
}
//进行原型继承
//Object.setPrototypeOf(ctor.prototype,superCtor.prototype);
//ctor.prototype.__proto__=superCtor.prototype;
util.inherits(Bell, EventEmitter)
let bell = new Bell();
function student(num, thing) {
  console.log(`学生${num}进教室${thing}`);
}
function teacher(num, thing) {
  console.log(`teacher进${num}教室${thing}`);
}
function dalidada(num, thing) {
  console.log(`dali进${num}教室${thing}`);
}
bell.setMaxListeners(10);
bell.addListenter('响', student);
bell.on('响', student);
bell.on('响', teacher);
bell.on('响', teacher);
bell.on('响', teacher);
bell.once('响', dalidada);
console.log('2222:', bell.listeners('响'));
bell.emit('响', '301', 'dada');
console.log('==========');
bell.emit('响', '301', 'dada');

debug调试

用命令行
node inspect test.js->n->s->o
//n代表next
//s代表进入
//o代表出
//repl可以通过输入变量名查看值,ctrl+c出来、
或者用watch 变量 然后watcher()

module

  • node通过require方法动态加载其他模块,是同步的
    require是common.js规范,import是es6(静态加载)。
    let school=require('./school');
    1.找到文件,2.读取文件内容,3.封装一个函数里立刻执行,4.把返回的值赋值给引用的变量
    node里的模块的类型有三种
    js,->转成函数
    json,->json.parse转成对象
    node,->转成二进制
    不写后缀,会按照[js,json,node]这样去查找对应的文件名
!function(exports,require,module,filename,dirname){
module.exports=代码块;
return module.exports

console.log(module)

  • id
  • exports
  • parent
  • children
  • filename
  • loaded:false,当异步打印的时候可以为true
    为什么是同步执行?
    因为模块实现了缓存,当第一次加载一个模块加载之后,会缓存这个模块的exports.再加载的时候会从缓存取。
    缓存的key是什么?是父模块的绝对路径

console.log(require)

  • require.resolve('./school')solve:只想知道路径不加载模块require.resolve('./school');
  • main:require.main();入口模块

原生模块 内置模块

内置模块在node.exe中,加载最快

  • fs
  • http
  • path
  • events
  • util
    文件模块:js,json,node,自己写的,第三方的(在node_module找)

encoding

8位=1字节,1024字节=1K
进制的转换
比如数字20的二进制0b10100;八进制0o24,十六进制0x14
parseInt('0x10',16)16转化为10进制
把十进制转化为任意进制:20.toString(2);

ASCII码
0-255,256种状态,0-32特殊用途,33-127所有国家通用,128-255为扩展字符
GB2312,(7998汉字),GBK
unicode统一编码,不管是中英文都是统一的一个字符也都是统一的两个字节
UTF-8每次都是8个位位单位传输数据

image.png

//把unicode码转为utf8
//比如'万'对应的UTF8码是4E07
let b = parseInt(0x4E08.toString(2))
console.log('b: ', b);
//b:100111000001000
//然后按照规则拆分
//11100100   10111000   10000111
console.log(0b11100100.toString(16));
console.log(0b10111000.toString(16));
console.log(0b10000111.toString(16));
console.log(Buffer.from('万'));
function transfer(number) {
  let arr = ['1110', '10', '10'];
  let str = number.toString(2);
  arr[2] += str.substring(str.length - 6);
  arr[1] += str.substring(str.length - 12, str.length - 6);
  arr[0] += str.substring(0, str.length - 12).padStart(4, '0');
 let result = arr.join('');
  console.log('result: ', result);
  return parseInt(result, 2).toString(16);
}
//unicode是16进制,所有的汉字都是3个字节
let r = transfer(0x4E08);
console.log('r: ', r);
//先转一下2进制

Buffer

//分配一个长度位6个字节的buffer
//会把所有的字节设置为0,也可以传第二个参数,进行默认
//可以提供默认值
let buf1 = Buffer.alloc(6, 2);
console.log('buf1: ', buf1);
//分配一块没有初始化的内存
let buf2 = Buffer.allocUnsafe(7);
console.log('buf2: ', buf2);
let buf3 = Buffer.from('大力');//一个汉字三个字节
console.log('buf3: ', buf3);
let buf4 = Buffer.alloc(5);
//1填充的值,2填充的开始索引,3结束索引
buf4.fill(3, 1, 3);//结束索引不包括本身索引
console.log('buf4: ', buf4);
console.log('========');
let buf5 = Buffer.alloc(6);
//1填充的值,2填充的开始索引,3填充的字节长度 4编码
buf5.write('大力', 0, 3, 'utf8')
// console.log(buf5.toString());
console.log('buf5: ', buf5);

let buf6 = Buffer.alloc(6);
buf6.writeInt8(0, 0);
buf6.writeInt8(16, 1);
buf6.writeInt8(32, 2);
console.log('buf6: ', buf6);

//buffer永远输出16进制,console永远输出10进制
//BE高位在前,LE是低位在前
let buf7 = Buffer.alloc(4);

buf7.writeInt16BE(256, 0);
console.log('buf7: ', buf7);
let s = buf7.readInt16BE(0);
//或者低位
// buf7.writeInt16LE(256, 2);
// console.log('buf7: ', buf7);
// let s = buf7.readInt16LE(2);

console.log('s: ', s);

console.log(Buffer.toString());
//buf的slice是浅拷贝
let buf8 = Buffer.alloc(6, 1);
console.log('buf8: ', buf8);
let buf9 = buf8.slice(2, 6);
console.log('buf9: ', buf9);
buf9.fill(4);
console.log('buf9: ', buf9);
console.log('buf8: ', buf8);
//string_decoder  是为了解决乱码问题

let buf10 = Buffer.from('我是大力');
let buf11 = buf10.slice(0, 5);
let buf12 = buf10.slice(5);
console.log('buf11: ', buf11.toString());
let { StringDecoder } = require('string_decoder');
let sd = new StringDecoder();
//write就是读取buffer的内容,返回一个字符串
//会判断是不是一个字符,是的话就输出,剩余的缓存,等下次write会把缓存放到里面
console.log(sd.write(buf11));
console.log(sd.write(buf12));


buffer.concat

Buffer.concat = function (list, total = list.reduce((pre, next) => pre + next.length, 0)) {
  if (list.length === 1) {
    return list[0];
  }
  let result = Buffer.alloc(total);
  let index = 0;
  for (let item of list) {
    for (let b of item) {
      if (index >= total) {
        return result
      } else {
        result[index++] = b;
      }
    }
  }
  return result;
}

let buf1 = Buffer.from('大');
let buf2 = Buffer.from('力');
let buf3 = Buffer.concat([buf1, buf2]);
console.log('buf3: ', buf3.toString());

fs

image.png

image.png
let fs = require('fs');
//flag你将要对这个文件进行哪种操作,r,r+,w,w+,wx,wx+,a,a+,ax,ax+
//r,r+,w,w+,wx,wx+,a,a+,ax,ax+
fs.readFile('./1.txt', { encoding: 'utf8', flag: 'w' }, function (err, data) {
  if (err) {
    console.log(err);
  } else {
    console.log(data);
    // console.log(data.toString());
  }
}) 
///666是指linux的权限类,只可读写权限,777可读可写可执行
fs.writeFile('./2.txt', '我是 dali', { encoding: 'utf8', flag: 'a', mode: 0o666 }, function (err) {
  if (err) {
    console.log(err);
  }
})
fs.appendFile('./2.txt', '我是 dali', { encoding: 'utf8', mode: 0o666 }, function (err) {
  if (err) {
    console.log(err);
  }
})
//fs.appendFile=fs.writeFile({flag:'a'})
//都是把文件当一个整体来操作
//当文件特别大的时候,大于内存的是无法执行这样的操作的
//=========================读
fs.open('./2.txt', 'r', 0o666, function (err, fd) {
  console.log(fd);
  let buff = Buffer.alloc(3);
  fs.read(fd, buff, 0, 3, 0, function (err, byt, buffer) {
    console.log('buff: ', buff.toString());
  })
  //position null 表示当前位置
  // let buff2 = Buffer.alloc(4);
  // fs.read(fd, buff2, 1, 3, null, function (err, byt, buffer) {
  //   console.log('buff: ', buff.toString());
  // })
})
//=====================写
fs.open('./2.txt', 'w', 0o666, function (err, fd) {
  console.log('err: ', err);
  let buff = Buffer.from('这是写入的内容');
//对应的参数是,3,3,0,分别是偏移字节,写几个字节,从哪字节索引开始
  fs.write(fd, buff, 3, 3, 0, function (err, byt, buffer) {
    console.log('byt: ', byt);
    console.log('err: ', err);
    console.log('buff: ', buff.toString());
  })

})
//==========读写
fs.open('./2.txt', 'r+', 0o666, function (err, fd) {
  console.log('err: ', err);
  let buff = Buffer.from('这是写入的内容');
  fs.write(fd, buff, 3, 3, 0, function (err, byt, buffer) {
    console.log('byt: ', byt);
    console.log('err: ', err);
    console.log('buff: ', buff.toString());
  })

})

文件copy

//为了节约内存的拷贝,读一点写一点,异步的
let fs = require('fs');
const BUFFER_SIZE = 6;//缓存大小6个字节
function copy(src, target) {
  fs.open(src, 'r', 0o666, function (err, readFd) {//读源文件
    fs.open(target, 'w', 0o666, function (err, writeFd) {//写目标文件
      let buff = Buffer.alloc(BUFFER_SIZE);
      !function next() {//异步自执行
        fs.read(readFd, buff, 0, BUFFER_SIZE, null, function (err, bytesRead, buffer) {//当源文件读取了缓存大小
          if (bytesRead > 0) {//读取后就开始往目标文件中写入读取的文件,并且回调
            fs.write(writeFd, buff, 0, bytesRead, null, next)
          }
        })
      }()

    })
  })

}
copy('1.txt', '2.txt');

读取到关闭

let fs = require('fs');
fs.open('./2.txt', 'w', 0o666, function (err, fd) {
  fs.write(fd, Buffer.from('dali'), 0, 4, null, function (err, bytesWrite) {
    console.log('bytesWrite: ', bytesWrite);
    fs.fsync(fd, function (err) {
      fs.close(fd, function () {
        console.log('关闭');
      })
    })

  })
})

异步创建文件夹

let fs = require('fs');
//创建一个文件夹,他的父级目录一定要存在并且可以操作
// fs.mkdir('a/b', function (err) {
//   console.log(err);
// });
// fs.access('a', fs.constants.R_OK, function (err) {
//   console.log('err: ', err);
// })
//递归异步创建目录
function mkdirp(dir) {
  let paths = dir.split('/');
  !function next(index) {
    if (index > paths.length) return;
    let current = paths.slice(0, index).join('/');
    fs.access(current, fs.constants.R_OK, function (err) {
      if (err) {
        fs.mkdir(current, 0o666, () => next(index + 1))
      } else {
        next(index + 1);
      }
    })
  }(1);

}
mkdirp('a/b/c');


node不支持解析GBK编码,需要借助第三方iconv-lite;

let iconv=require('iconv-lite');
fs.readFile('2.txt',function(err,data){
let str=iconv.decode(data,'gbk');
console.log(str)
})

读文件夹

let fs = require('fs');
let path = require('path');
fs.readdir('./a', function (err, files) {
  console.log('files: ', files);
  files.forEach(file => {
    let child = path.join('a', file);
    console.log('child: ', child);
    fs.stat(child, function (err, stat) {
      console.log('stat: ', stat);
    })
  })
})
  • fs.rename();
  • 删除文件 fs.unlink
  • 删除文件夹 fs.rmdir 这一定是一个非空目录
  • fs.truncate('./1.txt',5,()=>{console.log('截断文件')});//截断文件,比如文件中是1234567,截断后只有12345

同步,异步删除文件夹结构

同步递归删除非空文件夹

let fs = require('fs');
let path = require('path');
function rmdirSync(dir) {
  let files = fs.readdirSync(dir);
  files.forEach(item => {
    let child = fs.statSync(path.join(dir, item));
    if (child.isDirectory()) {
      rmdirSync(path.join(dir, item))
    } else {
      fs.unlinkSync(path.join(dir, item))
    }
  });
  fs.rmdirSync(dir)
}
rmdirSync('a')

异步递归删除非空文件夹

let fs = require('fs');
let path = require('path');
function rmdir(dir) {
  return new Promise(function (resolve, reject) {
    fs.stat(dir, (err, stat) => {
      if(err)return reject(err);   
      if (stat.isDirectory()) {
        fs.readdir(dir, (err, files) => {
          if (err) return reject(err);
          //先删除当前目录的子文件夹或文件,再删除自己
          Promise.all(files.map(item => rmdir(path.join(dir, item)))).then(() => {
            fs.rmdir(dir, resolve)
          });
        });
      } else {
        fs.unlink(dir, resolve)
      }
    }) 
  })
}
rmdir('a1');

  • 深度优先,广度优先

path

path.join(a,b);// ->a/b

let path = require('path');
//resolve从当前路径出发,解析出一个绝对路径
//..代表上一级目录
//.代表当前目录
//字符串a代表当前目录下面的a目录
console.log('path: ', path.resolve());// c:\Users\大力\Desktop\node
console.log('path: ', path.resolve('..', '.', 'a'));//c:\Users\大力\Desktop\a
//环境变量路径分隔符
//因为再不同的操作系统,分隔符不一样
//window;mac,linux;
console.log(path.delimiter);//;
console.log(path.win32.delimiter);//;
console.log(path.posix.delimiter);//:
//文件路径分隔符 
console.log(path.sep);//  '\'
console.log(path.win32.sep);// '\'
console.log(path.posix.sep);// '/'
//获取两个路径间的相对路径 path.relative
//basename获取的是文件名aa.jpg,第二个参数是要减去的扩展名
console.log(path.basename('aa.jpg'));
console.log(path.basename('aa.jpg', '.jpg'));
//extname获取的是文件的扩展名.jpg
console.log(path.extname('aa.jpg'));


Strean

可读流



//可读流
let fs = require('fs');
//创建可读流
let rs = fs.createReadStream('./1.txt', {
  highWaterMark: 3,//缓冲区大小
  flags: 'r',//什么操作
  mode: 0o666,//权限
  start: 3,//从索引为3的位置读
  end: 9,//从索引为9的位置结束,注意,这个end的值是包括索引对应的值的
});
//监听data事件,流开始读文件的内容并且发射data,默认情况下,监听data事件之后,会不停的读数据,
rs.on('error', function () {
  console.log('错了');
});
rs.on('open', function () {
  console.log('打开');
});
rs.on('data', function (data) {
  console.log('data: ', data.toString());
  rs.pause();//暂停读取和发射data事件
  setTimeout(function () {
    rs.resume();//恢复读取并触发data事件
  }, 2000)
});
rs.on('end', function () {
  console.log('读结束了');
});
rs.on('close', function () {
  console.log('关闭了');
});


可写流

//可写流
//也是先缓存,后写入
//根据缓存区的情况,会返回对应的true或者false,
//如果返回false就不能往里面写了,但是呢写了也不会丢失,会缓存在内存里,等缓存区清理掉,会从内存拿
let fs = require('fs');
let ws = fs.createWriteStream('./writeStream', {
  flags: 'w',
  highWaterMark: '3',
  mode: 0o666,
  start: 0,
});
//
let test = ws.write('1');
console.log('test: ', test);//test:  true
test = ws.write('2');
console.log('test: ', test);//test:  true
test = ws.write('3');
console.log('test: ', test);//test:  false
test = ws.write('4');
console.log('test: ', test);//test:  false


stream.pipe()管道流入到可写流的来源流。就是把可读的文件流写入到可写流

let fs = require('fs');
// //创建可读流
let rs = fs.createReadStream('./1.txt', {
  highWaterMark: 3,//缓冲区大小
  flags: 'r',//什么操作
  mode: 0o666,//权限
  start: 3,//从索引为3的位置读
  end: 9,//从索引为9的位置结束,注意,这个end的值是包括索引对应的值的
});
let ws = fs.createWriteStream('./writeStream.txt', {
  flags: 'w',
  highWaterMark: '3',
  mode: 0o666,
  start: 0,
});
rs.pipe(ws);//通过管道流入到可写流的来源流。就是把可读的文件流写入到可写流

stream.unpipe()取消管道传输
流动模式和暂停模式


let fs = require('fs');
let rs = fs.createReadStream('./1.txt', {
  highWaterMark: 3,//缓冲区大小

});
//可读流分为流动模式,暂停模式两种
//流动模式不缓存,直接发射,然后读取下次的结果,没有消费,数据就白白丢失了,可以理解为水龙头开了开关,一直流水。比如on('data'),pipe
//当监听readable事件的时候会进入暂停模式,可读流会马上去向底层读取文件,然后把读到的文件放在缓存区里
//self.read(0);只填充缓存区,但是不会法神data事件
// MediaStream.emit('readable');
rs.on('readable', function () {
  console.log('缓存:', rs._readableState.length);
  //read如果不加参数就是读取整个缓存区数据
  //读取一个字段,如果可读流发现你要读的字节小于等于缓存字节大小,直接返回
  let ch = rs.read(2);
  console.log('ch1: ', ch.toString());
  console.log('缓存2:', rs._readableState.length);
  //当读完指定得字节后,如果可读流发现剩下得字节已经比最高水位线小了,则会再次读取填满最高水位线,
`而缓存会累加,比如最大三个字节的缓存,读了一个还有两个,当监听到剩余的两个小于最大缓存,则会重新追加一份新的缓存,就是3-1=2,2+3=5个字节的缓存`
 
  setTimeout(function () {
    console.log('缓存3:', rs._readableState.length);//3-2+3=4个字节,
  }, 200)

})

封装按行读取文件

linereader这是封装的文件

//行读取,
//换行是两个字符window是0d,0a

// let fs = require('fs');
// fs.readFile('./1.txt', (err, data) => { 
//   console.log('data: ', data);
// })

//行读取器,
//写入一个类,然后可以传入一个文件路径得到类得实例,然后我们可以监听它的newLine事件,当这个行读取器每次读到一行得时候,就会向外发射newLine事件,当读到结束得时候会发射end事件
let fs = require('fs');
let EventEmitter = require('events');
let util = require('util');
const RETURN = 0x0d;// /r  回车
const NEW_LINE = 0x0a;// /n  换行

function LineReader(path, encoding) {
  this.encoding = encoding || 'utf8';
  EventEmitter.call(this);
  this._reader = fs.createReadStream(path);

  //当给一个对象添加一个新的监听函数得时候回发出newListener事件
  this.on('newListener', (type, listener) => {
    //如果添加了newLine和监听,那么就开始读取文件内容并按行发射数据
    if (type == 'newLine') {
      //当我们监听了一个可读流得readable,流会从底层得读取文件得API方法填充缓存去,填充完
      let buffers = [];
      this._reader.on('readable', () => {
        let char;//Buffer,是一个只有一个字节得buffer
        while (null !== (char = this._reader.read(1))) {
          switch (char[0]) {
            case NEW_LINE:
              this.emit('newLine', Buffer.from(buffers).toString(this.encoding));
              buffers.length = 0;
              break;
            case RETURN:
              this.emit('newLine', Buffer.from(buffers).toString(this.encoding));
              buffers.length = 0;
              //当是/r得时候再往后读一个字节
              let nChar = this._reader.read(1);
              if (nChar[0] !== NEW_LINE) {
                buffers.push(nChar[0])
              }
              break;
            default:
              buffers.push(char[0])
              break;
          }
        }
      })
      this._reader.on('end', () => {
        this.emit('newLine', Buffer.from(buffers).toString(this.encoding))
        this.emit('end')
      })

    }
  })
}
util.inherits(LineReader, EventEmitter);
module.exports = LineReader;

文件调用


let LineReader = require('./linereader.js');
let read = new LineReader('./1.txt', 'utf8');
read.on('newLine', (data) => {
  console.log('data: ', data);
});
read.on('end', () => {
  console.log('结束了');
})


手写可写流


let fs = require('fs');
let EventEmitter = require('events');
class WriteStream extends EventEmitter {
  constructor(path, options) {
    super(path, options);
    this.path = path;
    this.flags = options.flags || 'w';
    this.mode = options.mode || 0o666;
    this.buffers = [];
    this.encoding = options.encoding || 'utf8';
    this.start = options.start || 0;
    this.pos = this.start;//文件的写入索引
    this.highWaterMark = options.highWaterMark || 16 * 1024;
    this.autoClose = options.autoClose;
    this.writing = false;//表示内部正在写入数据
    this.length = 0;
    this.open();

  }
  open() {
    fs.open(this.path, this.flags, this.mode, (err, fd) => {
      if (err) {
        if (this.autoClose) {
          this.destroy();
        }
      return  this.emit('error', err);
      }
      this.fd = fd;
      this.emit('open');
    })
  }
  //如果底层已经写入数据的话,则必须当前要写入数据放在缓存区里
  write(chunk, encoding, cb) {
    chunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, this.encoding);
    let len = chunk.length;
    //缓存区的长度加上当前写入的长度
    this.length += len;
    //判断当前最新的缓存区是否小于最高水位线;
    let ret = this.length < this.highWaterMark;

    if (this.writing) {
      this.buffers.push({
        chunk,
        encoding,
        cb
      });

    } else {//直接调用底层的写入方法进行写入
      //在底层写完当前数据后要清空缓存区
      this.writing = true;
      this._write(chunk, encoding, () => this.clearBuffer())
    }
    return ret;
  }
  clearBuffer() {
    //取出缓存区的第一个buffer
    let data = this.buffers.shift();
    if (data) {
      this._write(data.chunk, data.encoding, () => this.clearBuffer())
    } else {
      //缓存区清空了
      this.writing = false;
      this.emit('drain')
    }

  }
  _write(chunk, encoding, cb) {
    if (typeof this.fd !== 'number') {
      return this.once('open', () => this._write(chunk, encoding, cb))
    }
    fs.write(this.fd, chunk, 0, chunk.length, this.pos, (err, bytesWritten) => {
      if (err) {
        if (this.autoClose) {
          this.destroy();
          this.emit('error', err);
        }
      }
      this.pos += bytesWritten;
      //写入多少字节,缓存区减少多少字节
      this.length -= bytesWritten;
      cb && cb();

    })
  }
  destroy() {
    fs.close(this.fd, () => {
      this.emit('close')
    })
  }
}
module.exports = WriteStream;


引用自己的手写流


let fs = require('fs');
//引入可写流
let WriteStream = require('./writeStream.js');
//这一行是用手写的可写流替换内置的可写流
let ws = new WriteStream('a.txt', {
  // let ws = fs.createWriteStream('a.txt', {
  highWaterMark: 3,
  flags: 'w',
  mode: 0o666,
  encoding: 'utf8',
  start: 0,
  autoClose: true,//当流写完自动关闭文件

});
let n = 9;
ws.on('error', err => {
  console.log('err: ', err);
})

function write() {
  let flag = true;
  while (flag && n > 0) {
 //这里的write方法会返回true或者false
    flag = ws.write(n + "", 'utf8', () => {
      // console.log('ok');
    });
    n--;
    console.log('flag: ', flag);
  }
}
//当flag为false就是没有缓存的时候会触发
ws.on('drain', () => {
  console.log('drain');
  write();
});
write();

手写可读流

流动性可读流


//流动模式不走缓存
let fs = require('fs');
let EventEmitter = require('events');
class ReadStream extends EventEmitter {
  constructor(path, options) {
    super(path, options);
    this.path = path;
    this.flags = options.flags || 'r';
    this.mode = options.mode || 0o666;
    this.highWaterMark = options.highWaterMark || 64 * 1024;
    this.encoding = options.encoding;
    this.start = options.start || 0;
    this.end = options.end;
    this.pos = this.start;//文件的写入索引
    this.flowing = null;
    this.buffer = Buffer.alloc(this.highWaterMark);
    this.open();
    //当给这个实例添加了任意的监听函数时会触发newlistener
    this.on('newListener', (type, listener) => {
      if (type === 'data') {
        this.flowing = true;
        this.read();

      }
    })

  }
  open() {
    fs.open(this.path, this.flags, this.mode, (err, fd) => {
      if (err) {
        if (this.autoClose) {
          this.destroy();
          return this.emit('error', err)
        }
      }
      this.fd = fd;
      this.emit('open');

    })
  }
  read() {
    if (typeof this.fd !== 'number') {
      return this.once('open', () => this.read())
    }
    let howMuchToRead = this.end ? Math.min(this.end - this.pos + 1, this.highWaterMark) : this.highWaterMark;
    //this.buffer并不是缓存区
    fs.read(this.fd, this.buffer, 0, howMuchToRead, this.pos, (err, bytesReaded) => {
      //bytesReaded是实际读到的字节数
      if (err) {
        if (this.autoClose) {
          this.destroy();
        }
        return this.emit('error', err);
      }
      if (bytesReaded) {
        let data = this.buffer.slice(0, bytesReaded);
        this.pos += bytesReaded;
        data = this.encoding ? data.toString(this.encoding) : bytesReaded;
        this.emit('data', data);
        if (this.end && this.pos > this.end) {
          // return this.emit('end');
          return this.endFn()
        } else {
          this.read()
        }
      } else {
        // return this.emit('end');
        return this.endFn()
      }

    })

  }
  endFn() {
    this.emit('end');
    this.destroy();
  }
  destroy() {
    fs.close(this.fd, () => {
      this.emit('close')
    });
  }
}
module.exports = ReadStream;


引用手写的流动型可读流

//流动模式
let fs = require('fs');
let ReadStream = require('./ReadStream.js');
let rs = new ReadStream('./a.txt', {
  // let rs = fs.createReadStream('./a.txt', {
  flags: 'r',
  mode: 0o666,
  start: 3,
  highWaterMark: 3,
  end: 8,//包括结束位置
  autoClose: true,
  encoding: 'utf8'
});
rs.on('open', () => {
  console.log('打开open');
})
rs.on('data', data => {
  console.log('data: ', data);
});
rs.on('end', () => {
  console.log('结束end');
})
rs.on('close', () => {
  console.log('close');
})
rs.on('error', () => {
  console.log('error');
})

pipe


//流动模式不走缓存
let fs = require('fs');
let EventEmitter = require('events');
class ReadStream extends EventEmitter {
  constructor(path, options) {
    super(path, options);
    this.path = path;
    this.flags = options.flags || 'r';
    this.mode = options.mode || 0o666;
    this.highWaterMark = options.highWaterMark || 64 * 1024;
    this.encoding = options.encoding;
    this.start = options.start || 0;
    this.end = options.end;
    this.pos = this.start;//文件的写入索引
    this.flowing = null;
    this.buffer = Buffer.alloc(this.highWaterMark);
    this.open();
    //当给这个实例添加了任意的监听函数时会触发newlistener
    this.on('newListener', (type, listener) => {
      if (type === 'data') {
        this.flowing = true;
        this.read();
      }
    })
  }
  open() {
    fs.open(this.path, this.flags, this.mode, (err, fd) => {
      if (err) {
        if (this.autoClose) {
          this.destroy();
          return this.emit('error', err)
        }
      }
      this.fd = fd;
      this.emit('open');
    })
  }
  read() {
    if (typeof this.fd !== 'number') {
      return this.once('open', () => this.read())
    }
    let howMuchToRead = this.end ? Math.min(this.end - this.pos + 1, this.highWaterMark) : this.highWaterMark;
    //this.buffer并不是缓存区
    console.log('howMuchToRead: ', howMuchToRead);
    fs.read(this.fd, this.buffer, 0, howMuchToRead, this.pos, (err, bytesReaded) => {
      //bytesReaded是实际读到的字节数
      if (err) {
        if (this.autoClose) {
          this.destroy();
        }
        return this.emit('error', err);
      }
      if (bytesReaded) {
        let data = this.buffer.slice(0, bytesReaded);
        this.pos += bytesReaded;
        data = this.encoding ? data.toString(this.encoding) : data;
        this.emit('data', data);
        if (this.end && this.pos > this.end) {
          // return this.emit('end');
          return this.endFn()
        } else {
          if (this.flowing)
            this.read();
        }
      } else {
        // return this.emit('end');
        return this.endFn()
      }
    })
  }
  endFn() {
    this.emit('end');
    this.destroy();
  }
  destroy() {
    fs.close(this.fd, () => {
      this.emit('close')
    });
  }
  pipe(dest) {
    this.on('data', data => {
      let flag = dest.write(data);
      if (!flag) {
        this.pause();//暂停
      }
    });
    dest.on('drain', () => {
      this.resume();//继续
    })
  }
  pause() {
    this.flowing = false;
  }
  resume() {
    this.flowing = true;
    this.read();
  }
}
module.exports = ReadStream;
-------------------------------------


let ReadStream = require('../read/ReadStream.js');
let WriteStream = require('../write/writeStream.js');
let rs = new ReadStream('./1.txt', {
  start: 3,
  end: 8,
  highWaterMark: 3,
});
let ws = new WriteStream('./2.txt', {
  highWaterMark: 3,
});
rs.pipe(ws)



自定义可读流

let { Readable } = require('stream');
let util = require('util');
util.inherits(Counter, Readable);
function Counter() {
  Readable.call(this);
  this.index = 3;
}
Counter.prototype._read = function () {
  if (this.index-- > 0) {
    this.push(this.index + '');
  } else {
    this.push(null)
  }
}
let counter = new Counter();
counter.on('data', function (data) {
  console.log('data: ', data.toString());
})

自定义可写流

let { Writable } = require('stream');
let arr = [];
let ws = Writable({
  write(chunk, encoding, cb) {
    arr.push(chunk);
    cb();
  }
});
for (let i = 0; i < 5; i++) {
  ws.write('' + i);
}
ws.end();
setTimeout(function () {
  console.log(arr)
}, 200)


pipe


let { Readable, Writable } = require('stream');
let i = 0;
let rs = Readable({
  highWaterMark: 2,
  read() {
    if (i < 10) { this.push('' + i++) } else { this.push(null) }
  },
});
let ws = Writable({
  highWaterMark: 2,
  write(chunk, encoding, cb) {
    console.log('chunk: ', chunk.toString());
    cb();//一定要执行回调
  },
});
rs.pipe(ws);
setTimeout(function () {
  console.log(rs._readableState.length);
  console.log(ws._writableState.length);
}, 200)

可读可写流Duplex

let { Duplex } = require('stream');
let index = 0;
let s = Duplex({
  read() {
    if (index++ < 3) {
      this.push('a');
    } else {
      this.push(null)
    }

  },
  write(chunk, encoding, cb) {
    console.log('chunk: ', chunk.toString().toUpperCase());
    cb();
  }
});
//process.stdin标准输入流
//process.stdout标准输出流
process.stdin.pipe(s).pipe(process.stdout);

转换流transform

let { Transform } = require('stream');
let t = Transform({
  transform(chunk, encoding, cb) {
    this.push(chunk.toString().toUpperCase())
    cb();
  }
})

process.stdin.pipe(t).pipe(process.stdout);

对象流 object

let { Transform } = require('stream');
let fs = require('fs');
let rs = fs.createReadStream('./user.json');
//普通流里放的是Buffer,对象流里放的是对象
let toJSON = Transform({
  readableObjectMode: true,//就可以向可读流里放对象
  transform(chunk, encoding, cb) {
    this.push(JSON.parse(chunk.toString()))
  }
})
let outJSON = Transform({
  writableObjectMode: true,//就可以向可写流里放对象
  transform(chunk, encoding, cb) {
    console.log('chunk: ', chunk);
    cb();

  }
})
rs.pipe(toJSON).pipe(outJSON);

你可能感兴趣的:(node笔记)