博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
nodeJs高阶模块--fs
阅读量:6255 次
发布时间:2019-06-22

本文共 8219 字,大约阅读时间需要 27 分钟。

请问大家: NodeJs只能做的两件事是什么?

这也能做,那也能做~
just joke~
实际上,所有后台语言都能做的两件事是: 文件操作和网络编程.
这其实是所有语言的根本。 计算机无外乎就是文件和通信。Linux中,是把一切都当做文件,如果理解了这一点那就无可厚非了.
所以,这里,我想介绍一下NodeJS中一个重要的模块--fs.
这里我给大家放一个我的框架图~
(为什么不是http? 懒~)
let's start.
针对于fs,我们切实围绕几个问题来吧~

  1. fs是如何操作文件的?

  2. drain和write到底是什么关系?

  3. fs怎么写出向gulp那样实时监听文件改变的插件?

关于fs的API,直接参考. 同样,放上fs的基本架构图:

(图有点大,大家另外开一个窗口看吧)
我们围绕这些问题来展开,说明吧.

fs操作文件的几种方式

这里,我们针对文件最基本的两种操作进行相关的解释和说明吧--read&&write

读写文件有哪几种操作方式呢?
我们先从最简便的开始吧~

先熟悉API: fs.createReadStream(path[, options]) path就是打开的文件路径,options有点复杂:

{  flags: 'r',  encoding: null,  fd: null,  mode: 0o666,  autoClose: true}

实际上我们一般也用不到options,除非你是获取已经打开后的文件.具体描述详见.官网.

ok~ 现在正式打开一个文件:

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,那些是dirconst 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()

大致梳理一下上面说的内容吧:

drain和stream.write的关联

首先这两个东西,是底层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一样监听文件变化的插件呢?

首先,我们看一下监听插件的配置:

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入手~
根据的解释,我们可以得出两个结论

  • 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//原始属性prevvar 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.htmlevent is rename and filename is app/index.html

en~ fs模块的基本内容就介绍到这里吧~ 大家有兴趣可以参考nodeJS官网. 由于nodeJs还年轻,以后的路还很长~ 刚把得~

转载地址:http://nrnsa.baihongyu.com/

你可能感兴趣的文章
org.apache.log4j.Logger 详解
查看>>
Tiny Linux -- tce-load
查看>>
Android 中自定义控件和属性(attr.xml,declare-styleable,TypedArray)的方法和使用
查看>>
vue中get和post请求
查看>>
2015-2016 ACM-ICPC, NEERC, Southern Subregional Contest A Email Aliases(模拟STL vector+map)
查看>>
Mr. Frog’s Game
查看>>
3.4可靠数据传输的原理
查看>>
多媒体通信-3-30-2018
查看>>
【Spring Boot&&Spring Cloud系列】Spring Boot中使用数据库之MySql
查看>>
【Spring Boot && Spring Cloud系列】那些Spring Boot中踩过的坑
查看>>
对XX系统的可用性和易用性改良
查看>>
大数据如何解决人工智能对文本挖掘的挑战
查看>>
updatepanel的属性
查看>>
.net 客户端调用java或.net webservice进行soapheader验证
查看>>
RadViz可视化方法--javascript实现
查看>>
软件工程综合实践的第二次实验报告
查看>>
Git储藏与恢复
查看>>
Lua 打印Table
查看>>
性能分析
查看>>
自定义php-mysqli工具增强类,支持链式调用
查看>>