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個位位單位傳輸數據
//把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
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);