请问你们: 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还年轻,之后的路还很长~ 刚把得~