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);
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容