請問你們: NodeJs只能作的兩件事是什麼?
這也能作,那也能作~
just joke~
實際上,全部後臺語言都能作的兩件事是: 文件操做和網絡編程.
這實際上是全部語言的根本。 計算機無外乎就是文件和通訊。Linux中,是把一切都當作文件,若是理解了這一點那就無可厚非了.
因此,這裏,我想介紹一下NodeJS中一個重要的模塊--fs.
這裏我給你們放一個個人框架圖~
(爲何不是http? 懶~)
let's start.
針對於fs,咱們切實圍繞幾個問題來吧~css
fs是如何操做文件的?html
drain和write究竟是什麼關係?node
fs怎麼寫出向gulp那樣實時監聽文件改變的插件?git
關於fs的API,直接參考Nodejs官網. 一樣,放上fs的基本架構圖:
(圖有點大,你們另外開一個窗口看吧)
咱們圍繞這些問題來展開,說明吧.github
這裏,咱們針對文件最基本的兩種操做進行相關的解釋和說明吧--read&&write
讀寫文件有哪幾種操做方式呢?
咱們先從最簡便的開始吧~ 編程
先熟悉API: fs.createReadStream(path[, options]) path就是打開的文件路徑,options有點複雜:gulp
{ flags: 'r', encoding: null, fd: null, mode: 0o666, autoClose: true }
實際上咱們通常也用不到options,除非你是獲取已經打開後的文件.具體描述詳見.官網.
ok~ 如今正式打開一個文件:api
const fs = require('fs'); const read = fs.createReadStream('sam.js',{encoding:'utf8'}); read.on('data',(str)=>{ console.log(str); }) read.on('end',()=>{ console.log('have already opened'); })
實際上,咱們就是利用fs繼承的readStream來進行操做的.數組
使用open打開文件
一樣上:API:
fs.open(path, flags[, mode], callback)這個和上面的readStream不一樣,open打開文件是一個持續狀態,至關於會將文件寫入到內存當中. 而readStream只是讀取文件,當讀取完畢時則會自動關閉文件--至關於fs.open+fs.close二者的結合~ 其中flags和mode 就是設置打開文件的權限,以及文件的權限模式(rwx).
使用open來打開一個文件網絡
const fs = require('fs'); fs.open('sam.js','r',(err,fd)=>{ fs.fstat(fd,(err,stat)=>{ var len = stat.size; //檢測文件長度 var buf = new Buffer(len); fs.read(fd,buf,0,len,0,(err,bw,buf)=>{ console.log(buf.toString('utf8')); fs.close(fd); }) }); });
使用相關的read/readdir/readFile/readlink
read方法,使用來讀取已經打開後的文件。 他不用用來進行打開文件操做,這點很重要》 那還有其餘方法,在讀的過程能夠直接打開文件嗎?
absolutely~
這裏就拿readFile和readdir舉例吧
API
fs.readFile(file[, options], callback): file就是文件路徑,options能夠爲object也能夠爲string. 不過最經常使用的仍是str. 咱們直接看demo:
const fs = require('fs'); fs.readFile('sam.js','utf8',(err,data)=>{ console.log(`the content is ,${data}`); })
另一個readdir,顧名思義該API就是用來讀取文件夾的.實際上,該API也沒有什麼卵用~
fs.readdir(path, callback):用來獲取文件下全部的文件(包括目錄),而且不會進行recursive.而且callback(err,files)中的files只是以數組的形式放回該目錄下全部文件的名字
show u code:
//用來檢查,上層目錄中那些是file,那些是dir const fs = require('fs'); fs.readdir('..', (err,files)=>{ var path,stat; files.map((val)=>{ path = `../${val}`; stat= fs.statSync(path); if(stat.isFile()){ console.log(`file includes ${val}`); }else if(stat.isDirectory()){ console.log(`dir includes ${val}`); } }) })
nodejs 打開文件的全部方式就是以上這幾種.接下來咱們再來看一下,若是寫入文件吧~
寫入文件
一樣,先介紹最簡單的吧.
fs.createWriteStream(path[, options]): path就是文件路徑.而options和上面的createWriteStream同樣比較複雜;
{ flags: 'w', defaultEncoding: 'utf8', fd: null, mode: 0o666 }
實際上,咱們只須要寫好path就enough了.
直接看demo吧:
//用來寫入str的操做 const fs = require('fs'); const write = fs.createWriteStream('sam.js'); write.on('drain',()=>{ write.resume(); }); var writeData = function(){ var i = 1000; while(i--){ if(!write.write('sam')){ write.pause(); } } } writeData();
實際上,上面那段代碼是最經常使用的寫入文件的寫法.drain是表明,寫入內存已經清空後,能夠繼續寫入時觸發的事件.這就是第二個問題: drain和write究竟是什麼關係? 這個問題,咱們放到後面講解,這裏先繼續說一下如何寫入內容.
使用fs.write方法直接寫入內容:
fs.writeAPI其實就有兩個:
fs.write(fd, buffer, offset, length[, position], callback):這一種,是用來直接寫入Buffer數據內容的.
fs.write(fd, data[, position[, encoding]], callback):這一種,是用來寫入str數據內容的.
不過,fs.write()該方法,也是創建在已有文件打開的基礎上的.
直接看一下demo:
//使用Buffer寫入 const fs = require('fs'); fs.open('sam.js','w+',(err,fd)=>{ var buf = new Buffer("sam",'utf8'); fs.write(fd,buf,0,buf.length,0,(err,bw,buf)=>{ fs.close(fd); }); }) //直接使用string寫入: const fs = require('fs'); fs.open('sam.js','w+',(err,fd)=>{ fs.write(fd,'sam','utf8',0,(err,bw,buf)=>{ fs.close(fd); }); })
一般狀況下,咱們也不會用來寫入Buffer的. 因此,第二種方法就足夠了.
同理,可否直接寫入未打開的文件呢?
固然是能夠的,因此這裏介紹最後一種方法. 使用writeFile和appendFile來寫入數據.
fs.writeFile(file, data[, options], callback):直接寫入指定文件. 寫入的內容會直接覆蓋掉原始內容.
fs.appendFile(file, data[, options], callback):真正的用來append file
//檢測文件是否存在,若是存在則增長內容,不然新建文件並寫入內容. const fs = require('fs'); var writeData = function() { fs.access('sam.js', (noAccess) => { if (noAccess) { fs.writeFile('sam.js', 'sam', (err) => { if (!err) console.log('writeFile success') }) } else { fs.appendFile('sam.js', 'sam', (err) => { if (!err) console.log('appendFile success~'); }); } }) } writeData()
大體梳理一下上面說的內容吧:
首先這兩個東西,是底層writeStream提供的. write這個方法不用解釋了吧~ 關鍵drain到底怎麼使用~ 這也是官網沒說清楚的地方:
If a stream.write(chunk) call returns false, then the 'drain' event will indicate when it is appropriate to begin writing more data to the stream.
實際上,咱們判斷用沒用到drain事件的機制,是根據write方法的返回值來進行判斷的. 官方也給出一個demo,用來測試drain事件的觸發.
const fs = require('fs'); const writer = fs.createWriteStream('sam.js'); writeOneMillionTimes(writer,'sam','utf8',()=>{});
沒錯,這樣確實會屢次觸發drain事件.可是,他究竟是何時會觸發呢?
根據源碼的介紹,write方法在使用時,會內置一個Buffer用來寫入數據.咱們能夠理解該Buffer就是該次寫入的最大內存值~ 那究竟是多少呢? 源碼:
var defaultHwm = this.objectMode ? 16 : 16 * 1024; //即,默認爲16KB
至關於,咱們每次寫入都會有16KB的空間開着~ 若是寫入data已經填滿了16KB, 而咱們還繼續寫入就有可能形成 memory leak~ 這就go die了。輕者卡一卡,重則死機都有可能. 那若是按照官網那種寫法的話,每次寫入一個大文件,都要寫入老長老長的函數呢?
倫家~纔不要~
實際上,咱們直接提煉一下,使用stream.once('drain')事件來進行處理.
if(!stream.write(data))stream.once('drain',()=>{ stream.write(otherData); })
或者當你使用readStream和writeStream用來讀寫文件時~可使用
//試一試讀取一個較大的文件,你會發現drain事件也會觸發~ 因此咱們須要使用pause和resume來暫停流的讀取,防止memory leak~ const fs = require('fs'); const read = fs.createReadStream('唱歌的孩子.mp3'); const write = fs.createWriteStream('get.mp3'); read.on('data',(chunk)=>{ if(!write.write(chunk))read.pause(); }); write.on('drain',()=>{ console.log('drain'); read.resume(); }) read.on('end',()=>{ console.log(`finish`); })
首先,咱們看一下監聽插件的配置:
gulp.task('sync', function() { var files = [ 'app/**/*.html', 'app/styles/**/*.css', 'app/img/**/*.png', 'app/src/**/*.js' ]; browserSync.init(files, { server: { baseDir: './app' } }); });
首先,咱們設置了files以後,就能夠監聽文件,而且開啓一個服務~
而實際上,就是使用Nodejs底層的fs.watch對文件進行監聽.咱們來使用fs.watch和fs.watchFile來實現文件的監聽~
這裏,咱們先從簡單的watchFile入手~
根據nitoyon的解釋,咱們能夠得出兩個結論
fs.watch() uses native API
fs.watchFile() periodically executes fs.stat()
因此,底層上來看,其實fs.watchFile是週期性執行fs.stat的,速度上來看,確定會慢的. 很少說了,咱們看一下demo:
const fs = require('fs'); fs.watchFile('sam.js', { persistent:true, interval:3000 }, (cur,prev)=>{ if(cur.mtime>prev.mtime){ console.log('change'); console.log(cur,prev); } })
這裏,主要想談及一下watchFile中的第二個參數,options中的interval. 這個東西有點傻逼~ 爲何呢? 由於,他並非在必定時間內,觸發watch,而是在第一次觸發後的interval時間內,不會觸發watch. 即,他會發生改變的積累~ 在interval時間內改變的內容,只會在最後一次中呈現出來~ 而他的底層其實就是調用fs.stat來完成的.這裏,咱們使用fs.stat來模仿一遍~
const fs = require('fs'), Event = require('events').EventEmitter, event = new Event(); //原始方法getCur //原始屬性prev var watchFile = function(file,interval,cb){ var pre,cur; var getPrv = function(file){ var stat = fs.statSync(file); return stat; } var getCur = function(file){ cur = getPrv(file); console.log(cur,pre); if(cur.mtime.toString()!==pre.mtime.toString()){ cb('change'); } pre = cur; //改變初始狀態 } var init = (function(){ pre = getPrv(file); //首先獲取pre event.on('change',function(){ getCur(file); }); setInterval(()=>{ event.emit('change'); },interval); })() } watchFile('sam.js',2000,function(eventname){ console.log(eventname); })
上述,完善了一下,在指定時間內,對文件改動進行監聽,和fs.watchFile不一樣.
ok~ 這個out-of-date的監聽方式,咱們大體瞭解了. 接下來咱們來看一下,如何使用v0.5.x版本退出的新API:fs.watch. 咱們參考官網:
fs.watch should be used instead of fs.watchFile and fs.unwatchFile when possible.
爲何呢?
不爲何. 由於,fs.watch調用的是native API。而fs.watchFile是調用的是fs.stat. 比起來,時間確定會慢一點.
那怎麼使用fs.watch監聽文件呢?
先看一下API吧:
fs.watch(filename, options):其實和fs.watchFile沒差多少. 很少options裏面有一個參數不一樣:
{ persistent: true, recursive: false }
即,該API不只能夠監聽文件,還能夠監聽目錄.其中recursive表示遞歸,用來監聽目錄下的文件。 不過NodeJS如是說:
The recursive option is only supported on OS X and Windows.
懂了吧. 不過基本上, 該API的覆蓋率也足夠了.別告訴我,你用linxu寫代碼.
const fs = require('fs'); fs.watch('..',{recursive:true},function(event,filename){ console.log(`event is ${event} and filename is ${filename}`); })
在MAC OX 11完美經過. 每當保存一次,就會觸發一次。不過當你修改文件名時,便會觸發兩次. 一次是,原文件被修改,另外一次是新文件被建立.即.
event is rename and filename is app/sam.html event is rename and filename is app/index.html
en~ fs模塊的基本內容就介紹到這裏吧~ 你們有興趣能夠參考nodeJS官網. 因爲nodeJs還年輕,之後的路還很長~ 剛把得~