Open xiaokeqi opened 4 years ago
文件操作属于node中的内置模块。几乎所有工具类或者编译类都需要操作文件。
先从copy文件来引入文件操作吧
var fs = require('fs') function copy(src, dst) { fs.writeFileSync(dst, fs.readFileSync(src)); } function main(argv) { copy(argv[0], argv[1]); } main(process.argv.slice(2));
以上通过文件的读readFileSync操作和写writeFileSync操作来实现
其中,process是node中的全局变量,[process.argv][http://nodejs.cn/api/process/process_argv.html][0]固定等于NodeJS执行程序的绝对路径,本机中为'/usr/local/bin/node',process.argv[1]固定等于主模块的绝对路径如/Users/mjr/work/node/process-args.js,因此第一个命令行参数从`argv[2]`这个位置开始。
上边的文件,拷贝一些小文件没有问题,若大文件,会内存爆满。因为其是一次性读取所有文件内容到内存中。因此对于大文件,我们只能读一些,写一些。节省内存消耗。
var fs = require('fs'); function copy(src, dst) { fs.createReadStream(src).pipe(fs.createWriteStream(dst)); } function main(argv) { copy(argv[0], argv[1]); } main(process.argv.slice(2));
以上程序使用fs.createReadStream创建了一个源文件的只读数据流,并使用fs.createWriteStream创建了一个目标文件的只写数据流,并且用pipe方法把两个数据流连接了起来。连接起来后发生的事情,说得抽象点的话,水顺着水管从一个桶流到了另一个桶。
fs.createReadStream
fs.createWriteStream
pipe
JS语言自身只有字符串数据类型,没有二进制数据类型,因此nodejs提供了一个与String对等的全局构造函数Buffer来提供对二进制数据的操作。除了可以读取文件,得到二进制实例外,还可以直接构造,如下
var bin = new Buffer([0x68, 0x65, 0x6c, 0x6c, 0x6f])
Buffer与字符串类似,除了可以用.length属性得到字节长度外,还可以用[index],读取指定位置的字节。如下
bin[0]; // => 0x68;
Buffer可以与字符串互相转化,如下:
var str = bin.toString('utf-8') // buffer转字符串 var bin = new Buffer('hello', 'utf-8') // 字符串转buffer
Buffer与字符串操作的一个重要区别是,字符串是只读的,任何对字符串的操作,均是返回一个新的字符串。而buffer更像是指针操作,直接修改当前buffer中的值。
而.slice方法也不是返回一个新的Buffer,而更像是返回了指向原Buffer中间的某个位置的指针,如下所示。
.slice
Buffer
因此对.slice方法返回的Buffer的修改会作用于原Buffer,例如:
var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]); var sub = bin.slice(2); sub[0] = 0x65; console.log(bin); // => <Buffer 68 65 65 6c 6f>
也因此,如果想要拷贝一份Buffer,得首先创建一个新的Buffer,并通过.copy方法把原Buffer中的数据复制过去。这个类似于申请一块新的内存,并把已有内存中的数据复制过去。以下是一个例子。
.copy
var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]); var dup = new Buffer(bin.length); bin.copy(dup); dup[0] = 0x48; console.log(bin); // => <Buffer 68 65 6c 6c 6f> console.log(dup); // => <Buffer 48 65 65 6c 6f>
总之,Buffer将JS的数据处理能力从字符串扩展到了任意二进制数据。
当内存中无法一次装下需要处理的数据时,或者一边读取一边处理更加高效时,我们就需要用到数据流。NodeJS中通过各种Stream来提供对数据流的操作。
Stream
以上边的大文件拷贝程序为例,我们可以为数据来源创建一个只读数据流,示例如下:
var rs = fs.createReadStream(pathname); rs.on('data', function (chunk) { doSomething(chunk); }); rs.on('end', function () { cleanUp(); });
Stream基于事件机制工作,所有Stream的实例都继承于NodeJS提供的EventEmitter。
上边的代码中data事件会源源不断地被触发,不管doSomething函数是否处理得过来。代码可以继续做如下改造,以解决这个问题。
data
doSomething
var rs = fs.createReadStream(src); rs.on('data', function (chunk) { rs.pause(); doSomething(chunk, function () { rs.resume(); }); }); rs.on('end', function () { cleanUp(); });
以上代码给doSomething函数加上了回调,因此我们可以在处理数据前暂停数据读取,并在处理数据后继续读取数据。
此外,我们也可以为数据目标创建一个只写数据流,示例如下:
var rs = fs.createReadStream(src); var ws = fs.createWriteStream(dst); rs.on('data', function (chunk) { ws.write(chunk); }); rs.on('end', function () { ws.end(); });
我们把doSomething换成了往只写数据流里写入数据后,以上代码看起来就像是一个文件拷贝程序了。但是以上代码存在上边提到的问题,如果写入速度跟不上读取速度的话,只写数据流内部的缓存会爆仓。我们可以根据.write方法的返回值来判断传入的数据是写入目标了,还是临时放在了缓存了,并根据drain事件来判断什么时候只写数据流已经将缓存中的数据写入目标,可以传入下一个待写数据了。因此代码可以改造如下:
.write
drain
var rs = fs.createReadStream(src); var ws = fs.createWriteStream(dst); rs.on('data', function (chunk) { if (ws.write(chunk) === false) { rs.pause(); } }); rs.on('end', function () { ws.end(); }); ws.on('drain', function () { rs.resume(); });
以上代码实现了数据从只读数据流到只写数据流的搬运,并包括了防爆仓控制。因为这种使用场景很多,例如上边的大文件拷贝程序,NodeJS直接提供了.pipe方法来做这件事情,其内部实现方式与上边的代码类似。
.pipe
NodeJS通过fs内置模块提供对文件的操作。fs模块提供的API基本上可以分为以下三类:
fs
文件属性读写。
其中常用的有fs.stat、fs.chmod、fs.chown等等。
fs.stat
fs.chmod
fs.chown
文件内容读写。
其中常用的有fs.readFile、fs.readdir、fs.writeFile、fs.mkdir等等。
fs.readFile
fs.readdir
fs.writeFile
fs.mkdir
底层文件操作。
其中常用的有fs.open、fs.read、fs.write、fs.close等等。
fs.open
fs.read
fs.write
fs.close
NodeJS最精华的异步IO模型在fs模块里有着充分的体现,例如上边提到的这些API都通过回调函数传递结果。以fs.readFile为例:
fs.readFile(pathname, function (err, data) { if (err) { // Deal with error. } else { // Deal with data. } });
如上边代码所示,基本上所有fs模块API的回调参数都有两个。第一个参数在有错误发生时等于异常对象,第二个参数始终用于返回API方法执行结果。
此外,fs模块的所有异步API都有对应的同步版本,用于无法使用异步操作时,或者同步操作更方便时的情况。同步API除了方法名的末尾多了一个Sync之外,异常对象与执行结果的传递方式也有相应变化。同样以fs.readFileSync为例:
Sync
fs.readFileSync
try { var data = fs.readFileSync(pathname); // Deal with data. } catch (err) { // Deal with error. }
将传入的路径转换为标准路径,具体讲的话,除了解析路径中的.与..外,还能去掉多余的斜杠。如果有程序需要使用路径作为某些数据的索引,但又允许用户随意输入路径时,就需要使用该方法保证路径的唯一性。以下是一个例子:
.
..
var cache = {}; function store(key, value) { cache[path.normalize(key)] = value; } store('foo/bar', 1); store('foo//baz//../bar', 2); console.log(cache); // => { "foo/bar": 2 }
坑出没注意: 标准化之后的路径里的斜杠在Windows系统下是\,而在Linux系统下是/。如果想保证任何系统下都使用/作为路径分隔符的话,需要用.replace(/\\/g, '/')再替换一下标准路径。
\
/
.replace(/\\/g, '/')
将传入的多个路径拼接为标准路径。该方法可避免手工拼接路径字符串的繁琐,并且能在不同系统下正确使用相应的路径分隔符。以下是一个例子:
path.join('foo/', 'baz/', '../bar'); // => "foo/bar"
path.extname
当我们需要根据不同文件扩展名做不同操作时,该方法就显得很好用。以下是一个例子:
path.extname('foo/bar.js'); // => ".js"
path模块提供的其余方法也不多,稍微看一下官方文档就能全部掌握。
path
文件操作
文件操作属于node中的内置模块。几乎所有工具类或者编译类都需要操作文件。
先从copy文件来引入文件操作吧
小文件拷贝
以上通过文件的读readFileSync操作和写writeFileSync操作来实现
其中,process是node中的全局变量,[process.argv][http://nodejs.cn/api/process/process_argv.html][0]固定等于NodeJS执行程序的绝对路径,本机中为'/usr/local/bin/node',process.argv[1]固定等于主模块的绝对路径如/Users/mjr/work/node/process-args.js,因此第一个命令行参数从`argv[2]`这个位置开始。
大文件拷贝
上边的文件,拷贝一些小文件没有问题,若大文件,会内存爆满。因为其是一次性读取所有文件内容到内存中。因此对于大文件,我们只能读一些,写一些。节省内存消耗。
以上程序使用
fs.createReadStream
创建了一个源文件的只读数据流,并使用fs.createWriteStream
创建了一个目标文件的只写数据流,并且用pipe
方法把两个数据流连接了起来。连接起来后发生的事情,说得抽象点的话,水顺着水管从一个桶流到了另一个桶。API走马观花
Buffer
JS语言自身只有字符串数据类型,没有二进制数据类型,因此nodejs提供了一个与String对等的全局构造函数Buffer来提供对二进制数据的操作。除了可以读取文件,得到二进制实例外,还可以直接构造,如下
Buffer与字符串类似,除了可以用.length属性得到字节长度外,还可以用[index],读取指定位置的字节。如下
Buffer可以与字符串互相转化,如下:
Buffer与字符串操作的一个重要区别是,字符串是只读的,任何对字符串的操作,均是返回一个新的字符串。而buffer更像是指针操作,直接修改当前buffer中的值。
而
.slice
方法也不是返回一个新的Buffer
,而更像是返回了指向原Buffer
中间的某个位置的指针,如下所示。因此对
.slice
方法返回的Buffer
的修改会作用于原Buffer
,例如:也因此,如果想要拷贝一份
Buffer
,得首先创建一个新的Buffer
,并通过.copy
方法把原Buffer
中的数据复制过去。这个类似于申请一块新的内存,并把已有内存中的数据复制过去。以下是一个例子。总之,
Buffer
将JS的数据处理能力从字符串扩展到了任意二进制数据。Stream数据流
当内存中无法一次装下需要处理的数据时,或者一边读取一边处理更加高效时,我们就需要用到数据流。NodeJS中通过各种
Stream
来提供对数据流的操作。以上边的大文件拷贝程序为例,我们可以为数据来源创建一个只读数据流,示例如下:
Stream
基于事件机制工作,所有Stream
的实例都继承于NodeJS提供的EventEmitter。上边的代码中
data
事件会源源不断地被触发,不管doSomething
函数是否处理得过来。代码可以继续做如下改造,以解决这个问题。以上代码给
doSomething
函数加上了回调,因此我们可以在处理数据前暂停数据读取,并在处理数据后继续读取数据。此外,我们也可以为数据目标创建一个只写数据流,示例如下:
我们把
doSomething
换成了往只写数据流里写入数据后,以上代码看起来就像是一个文件拷贝程序了。但是以上代码存在上边提到的问题,如果写入速度跟不上读取速度的话,只写数据流内部的缓存会爆仓。我们可以根据.write
方法的返回值来判断传入的数据是写入目标了,还是临时放在了缓存了,并根据drain
事件来判断什么时候只写数据流已经将缓存中的数据写入目标,可以传入下一个待写数据了。因此代码可以改造如下:以上代码实现了数据从只读数据流到只写数据流的搬运,并包括了防爆仓控制。因为这种使用场景很多,例如上边的大文件拷贝程序,NodeJS直接提供了
.pipe
方法来做这件事情,其内部实现方式与上边的代码类似。文件系统File System
NodeJS通过
fs
内置模块提供对文件的操作。fs
模块提供的API基本上可以分为以下三类:文件属性读写。
其中常用的有
fs.stat
、fs.chmod
、fs.chown
等等。文件内容读写。
其中常用的有
fs.readFile
、fs.readdir
、fs.writeFile
、fs.mkdir
等等。底层文件操作。
其中常用的有
fs.open
、fs.read
、fs.write
、fs.close
等等。NodeJS最精华的异步IO模型在
fs
模块里有着充分的体现,例如上边提到的这些API都通过回调函数传递结果。以fs.readFile
为例:如上边代码所示,基本上所有
fs
模块API的回调参数都有两个。第一个参数在有错误发生时等于异常对象,第二个参数始终用于返回API方法执行结果。此外,
fs
模块的所有异步API都有对应的同步版本,用于无法使用异步操作时,或者同步操作更方便时的情况。同步API除了方法名的末尾多了一个Sync
之外,异常对象与执行结果的传递方式也有相应变化。同样以fs.readFileSync
为例:Path(路径)
将传入的路径转换为标准路径,具体讲的话,除了解析路径中的
.
与..
外,还能去掉多余的斜杠。如果有程序需要使用路径作为某些数据的索引,但又允许用户随意输入路径时,就需要使用该方法保证路径的唯一性。以下是一个例子:将传入的多个路径拼接为标准路径。该方法可避免手工拼接路径字符串的繁琐,并且能在不同系统下正确使用相应的路径分隔符。以下是一个例子:
path.extname
当我们需要根据不同文件扩展名做不同操作时,该方法就显得很好用。以下是一个例子:
path
模块提供的其余方法也不多,稍微看一下官方文档就能全部掌握。