开发中咱们常常会有文件I/O的需求,node.js中提供一个名为fs的模块来支持I/O操做,fs模块的文件I/O是对标准POSIX函数的简单封装。css
文件I/O,写入是必修课之一。fs模块提供writeFile函数,能够异步的将数据写入一个文件, 若是文件已经存在则会被替换。用法以下:node
例:fs.writeFile(filename, data, callback)
linux
var fs= require("fs"); fs.writeFile('test.txt', 'Hello Node', function (err) { if (err) throw err; console.log('Saved successfully'); //文件被保存 });
writeFile函数虽然能够写入文件,可是若是文件已经存在,咱们只是想添加一部份内容,它就不能知足咱们的需求了,很幸运,fs模块中还有appendFile函数,它能够将新的内容追加到已有的文件中,若是文件不存在,则会建立一个新的文件。使用方法以下:web
例:fs.appendFile(文件名,数据,编码,回调函数(err));
算法
var fs= require("fs"); fs.appendFile('test.txt', 'data to append', function (err) { if (err) throw err; //数据被添加到文件的尾部 console.log('The "data to append" was appended to file!'); });
编码格式默认为"utf8"apache
如何检查一个文件是否存在呢?我想exists函数能够帮助你,用法以下:tomcat
例:fs.exists(文件,回调函数(exists));
app
exists的回调函数只有一个参数,类型为布尔型,经过它来表示文件是否存在。webapp
var fs= require("fs"); fs.exists('/etc/passwd', function (exists) { console.log(exists ? "存在" : "不存在!"); });
修改文件名称是咱们常常会碰见的事情,rename函数提供修更名称服务:异步
var fs= require("fs"); fs.rename(旧文件,新文件,回调函数(err)){ if (err) throw err; console.log('Successful modification,'); });
移动文件也是咱们常常会碰见的,但是fs没有专门移动文件的函数,可是咱们能够经过rename函数来达到移动文件的目的,示例以下。
var fs = require('fs'); fs.rename(oldPath,newPath,function (err) { if (err) throw err; console.log('renamed complete'); });
读取文件是最经常使用到的功能之一,使用fs模块读取文件语法以下: 例:fs.readFile(文件,编码,回调函数)
;
var fs = require('fs'); fs.readFile(文件名, function (err, data) { if (err) throw err; console.log(data); });
回调函数里面的data,就是读取的文件内容。
面对一堆垃圾的文件老是有想删除的冲动,我有强迫症?你才有呢。 好在有unlink函数,终于得救了,示例以下:
例:fs.unlink(文件,回调函数(err))
;
var fs = require('fs'); fs.unlink(文件, function(err) { if (err) throw err; console.log('successfully deleted'); });
除了针对文件的操做,目录的建立、删除也常常遇到的,下面咱们来看看node.js中如何建立目录:
fs.mkdir(路径,权限,回调函数(err))
;
参数
路径:新建立的目录。
权限:可选参数,只在linux下有效,表示目录的权限,默认为0777,表示文件全部者、文件全部者所在的组的*用户、*全部用户,都有权限进行读、写、执行的操做。
回调函数:当发生错误时,错误信息会传递给回调函数的err参数。
删除目录也是必不可少的功能,rmdir函数能够删除指定的目录:
例:fs.rmdir(路径,回调函数(err));
var fs = require('fs'); fs.rmdir(path, function(err) { if (err) throw err; console.log('ok'); });
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));
以上程序使用 fs.readFileSync 从源路径读取文件内容,并使用 fs.writeFileSync 将文件内容写入目标路径。
豆知识: process 是一个全局变量,可经过 process.argv
得到命令行参数。因为 argv[0]
固定等于 NodeJS 执行程序的绝对路径,argv[1]
固定等于主模块的绝对路径,所以第一个命令行参数从 argv[2]
这个位置开始。
上边的程序拷贝一些小文件没啥问题,但这种一次性把全部文件内容都读取到内存中后再一次性写入磁盘的方式不适合拷贝大文件,内存会爆仓。对于大文件,咱们只能读一点写一点,直到完成拷贝。所以上边的程序须要改造以下。
ar 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 方法把两个数据流链接了起来。链接起来后发生的事情,说得抽象点的话,水顺着水管从一个桶流到了另外一个桶。
遍历目录是操做文件时的一个常见需求。好比写一个程序,须要找到并处理指定目录下的全部JS文件时,就须要遍历整个目录。
遍历目录时通常使用递归算法,不然就难以编写出简洁的代码。递归算法与数学概括法相似,经过不断缩小问题的规模来解决问题。如下示例说明了这种方法。
function factorial(n) { if (n === 1) { return 1; } else { return n * factorial(n - 1); } }
上边的函数用于计算 N 的阶乘(N!)。能够看到,当 N 大于 1 时,问题简化为计算 N 乘以 N-1 的阶乘。当 N 等于 1 时,问题达到最小规模,不须要再简化,所以直接返回 1。
陷阱: 使用递归算法编写的代码虽然简洁,但因为每递归一次就产生一次函数调用,在须要优先考虑性能时,须要把递归算法转换为循环算法,以减小函数调用次数。
目录是一个树状结构,在遍历时通常使用深度优先+先序遍历算法。深度优先,意味着到达一个节点后,首先接着遍历子节点而不是邻居节点。先序遍历,意味着首次到达了某节点就算遍历完成,而不是最后一次返回某节点才算数。所以使用这种遍历方式时,下边这棵树的遍历顺序是 A > B > D > E > C > F。
A / \ B C / \ \ D E F
了解了必要的算法后,咱们能够简单地实现如下目录遍历函数。
function travel(dir, callback) { fs.readdirSync(dir).forEach(function (file) { var pathname = path.join(dir, file); if (fs.statSync(pathname).isDirectory()) { travel(pathname, callback); } else { callback(pathname); } }); }
能够看到,该函数以某个目录做为遍历的起点。遇到一个子目录时,就先接着遍历子目录。遇到一个文件时,就把文件的绝对路径传给回调函数。回调函数拿到文件路径后,就能够作各类判断和处理。所以假设有如下目录:
- /home/user/ - foo/ x.js - bar/ y.js z.css
使用如下代码遍历该目录时,获得的输入以下。
travel('/home/user', function (pathname) { console.log(pathname); });
/home/user/foo/x.js /home/user/bar/y.js /home/user/z.css
若是读取目录或读取文件状态时使用的是异步API,目录遍历函数实现起来会有些复杂,但原理彻底相同。travel函数的异步版本以下。
function travel(dir, callback, finish) { fs.readdir(dir, function (err, files) { (function next(i) { if (i < files.length) { var pathname = path.join(dir, files[i]); fs.stat(pathname, function (err, stats) { if (stats.isDirectory()) { travel(pathname, callback, function () { next(i + 1); }); } else { callback(pathname, function () { next(i + 1); }); } }); } else { finish && finish(); } }(0)); }); }
var async = require("async"); var fs = require("fs"); var path = require("path"); // cursively make dir function mkdirs(p, mode, f, made) { if (typeof mode === 'function' || mode === undefined) { f = mode; mode = 777 & (~process.umask()); } if (!made) made = null; var cb = f || function () {}; if (typeof mode === 'string') mode = parseInt(mode, 8); p = path.resolve(p); fs.mkdir(p, mode, function (er) { if (!er) { made = made || p; return cb(null, made); } switch (er.code) { case 'ENOENT': mkdirs(path.dirname(p), mode, function (er, made) { if (er) { cb(er, made); } else { mkdirs(p, mode, cb, made); } }); break; // In the case of any other error, just see if there's a dir // there already. If so, then hooray! If not, then something // is borked. default: fs.stat(p, function (er2, stat) { // if the stat fails, then that's super weird. // let the original error be the failure reason. if (er2 || !stat.isDirectory()) { cb(er, made); } else { cb(null, made) }; }); break; } }); } // single file copy function copyFile(file, toDir, cb) { async.waterfall([ function (callback) { fs.exists(toDir, function (exists) { if (exists) { callback(null, false); } else { callback(null, true); } }); }, function (need, callback) { if (need) { mkdirs(path.dirname(toDir), callback); } else { callback(null, true); } }, function (p, callback) { var reads = fs.createReadStream(file); var writes = fs.createWriteStream(path.join(path.dirname(toDir), path.basename(file))); reads.pipe(writes); //don't forget close the when all the data are read reads.on("end", function () { writes.end(); callback(null); }); reads.on("error", function (err) { console.log("error occur in reads"); callback(true, err); }); } ], cb); } // cursively count the files that need to be copied function _ccoutTask(from, to, cbw) { async.waterfall([ function (callback) { fs.stat(from, callback); }, function (stats, callback) { if (stats.isFile()) { cbw.addFile(from, to); callback(null, []); } else if (stats.isDirectory()) { fs.readdir(from, callback); } }, function (files, callback) { if (files.length) { for (var i = 0; i < files.length; i++) { _ccoutTask(path.join(from, files[i]), path.join(to, files[i]), cbw.increase()); } } callback(null); } ], cbw); } // wrap the callback before counting function ccoutTask(from, to, cb) { var files = []; var count = 1; function wrapper(err) { count--; if (err || count <= 0) { cb(err, files) } } wrapper.increase = function () { count++; return wrapper; } wrapper.addFile = function (file, dir) { files.push({ file : file, dir : dir }); } _ccoutTask(from, to, wrapper); } function copyDir(from, to, cb) { if(!cb){ cb=function(){}; } async.waterfall([ function (callback) { fs.exists(from, function (exists) { if (exists) { callback(null, true); } else { console.log(from + " not exists"); callback(true); } }); }, function (exists, callback) { fs.stat(from, callback); }, function (stats, callback) { if (stats.isFile()) { // one file copy copyFile(from, to, function (err) { if (err) { // break the waterfall callback(true); } else { callback(null, []); } }); } else if (stats.isDirectory()) { ccoutTask(from, to, callback); } }, function (files, callback) { // prevent reaching to max file open limit async.mapLimit(files, 10, function (f, cb) { copyFile(f.file, f.dir, cb); }, callback); } ], cb); } var start = new Date().getTime(); var src = "D:\\MyWork\\wepass_wx\\platform\\wpwx\\WebRoot"; var dist = "D:\\servers\\apache-tomcat-8.0.35\\webapps\\wpwx" copyDir(src, dist, function (err) { if (err) { console.log("error ocur"); console.dir(err); } else { console.log("copy ok"); console.log("consume time:" + (new Date().getTime() - start)) } });