開發中咱們常常會有文件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)) } });