Node
的檔案處理涉及到前面說的ptah
模組,以及fs
檔案系統、stream
流處理、Buffer
緩衝器等模組。內容可能比較多,相關內容請以官網文件為主,此處主要以案例講解為主,分享給大家一些常用的經典案例。細節就不展開了。
fs檔案系統
fs
模組提供了很多檔案操作相關的api,比如:監控資料夾、檔案,檔案重新命名,檔案讀寫,檔案修改許可權、檔案讀寫流等。
在此,我們僅以幾個案例的方式來驅動學習Node的檔案系統,細節請詳細閱讀Node的api文件或者原始碼。
案例:
-
如何監控資料夾的變化?
-
如何讀取一個檔案?
-
如何把內容寫入另外一個檔案?
-
檔案件讀取、檔案重新命名、移動等各種功能
如何監控資料夾的變化?
fs
模組提供了FSWatcher
類輔助我們進行監控資料夾,可以通過fs.watch()
方法返回此型別例項。然後通過註冊相關的事件回撥函式達到對檔案變化的監控。不廢話,直接上程式碼,詳細內容請參考官方文件。
// 引入fs模組
const fs = require('fs');
// 通過fs.watch方法可以建立一個fs.FSWatcher類的例項。
let watcher = fs.watch(
__dirname, // 監控的資料夾,我這裡用了一個模組的變數,當前js檔案所在的目錄
{ recursive: true }, // 是否監控子資料夾,還可以設定編碼,具體參考官網文件
(eventName, fileName) => { // 回撥函式接受兩個引數:事件名字和檔名
console.log(`事件名字:${eventName}, 檔名字: ${fileName}`);
}
);
// 還可以單獨註冊事件,回撥函式跟watch方法一致。還可以監聽:error事件。
watcher.on('change', (eventType, fileName) => {
console.log('事件名:%s , 檔名: %s', eventType, fileName);
});
// 設定13秒中後,退出監控資料夾
setTimeout(() => {
// 關閉監控。
watcher.close(function(err) {
if (err) {
console.error(err);
}
console.log('關閉watch');
});
}, 13000);
/********
以下是輸出的結果:當js檔案修改的時候
事件名字:change, 檔名字: 05filewatch.js
事件名:change , 檔名: 05filewatch.js
事件名字:change, 檔名字: 05filewatch.js
事件名:change , 檔名: 05filewatch.js
*/
如何讀取一個檔案?
讀取檔案可以分為小檔案讀取和大檔案,如果小檔案可以一次性的把檔案內容讀取出來然後再處理,對於比較大的檔案用檔案流處理。
一次性讀取小檔案的方法
fs.readFile()
方法可以幫助我們一次性的把檔案裡面內容讀取出來。文件
const fs = require('fs');
const path = require('path');
// 把當前js所在的目錄中的a.html檔案的路徑賦值給 fileName
let fileName = path.join(__dirname, 'a.html');
// 讀取a.html檔案,按照utf8的編碼方式讀取。回撥函式第一個引數是err(這個是一個預設的約定規範,大多數node
// 的回撥函式第一個引數都是異常的err,如果為空則表示沒有錯誤。)第二個引數是檔案的所有內容。
fs.readFile(fileName, { encoding: 'utf8' }, (err, data) => {
if (err) throw err; // 判斷是否讀取錯誤
console.log(data); // 檔案內容讀取並列印到控制檯。
});
//readFileSync方法是readFile的同步版本,沒有回撥函式,函式的返回值就是檔案內容。
// 以下程式碼是同步讀取,不使用回撥函式,此方法不是用的: libuv 的執行緒池的執行緒執行,所以慎用!!
let fileContent = fs.readFileSync(fileName, { encoding: 'utf8' });
console.log(fileContent);
注意:此方法只適合比較小的檔案,不適合大檔案讀取。
同步方法儘量少用,非同步的讀取檔案都是利用了libuv 的執行緒池的執行緒讀取檔案,所以讀取檔案等待期間不會阻塞主執行緒的事件迴圈。
讀取大檔案
使用stream讀取大檔案。當然你可以自定義可讀流,也可以用node內建的建立可讀流的api。
const fs = require('fs');
const path = require('path');
// 把當前js所在的目錄中的a.html檔案的路徑賦值給 fileName
let fileName = path.join(__dirname, 'a.html');
// 建立可讀流
let readStream = fs.createReadStream(fileName, {
flags: 'r', // 設定檔案只讀模式開啟檔案
encoding: 'utf8' // 設定讀取檔案的內容的編碼
});
// 開啟檔案流的事件。
readStream.on('open', fd => {
console.log('檔案可讀流已開啟,控制程式碼:%s', fd);
});
// 可讀流開啟後,會源源不斷的觸發此事件方法,回撥函式引數就是讀取的資料。
readStream.on('data', data => {
console.log(data);
});
readStream.on('close', () => {
console.log('檔案可讀流結束!');
});
如何寫入檔案
寫入檔案也是一樣的,如果是比較少的內容可以一次性寫入檔案。其他情況可以用流、管道等方式解決。
一次性寫入少量內容到檔案
const fs = require('fs');
const path = require('path');
// 把當前js所在的目錄中的a.html檔案的路徑賦值給 fileName
let fileName = path.join(__dirname, 'a.html');
// 寫入檔案
fs.writeFile(
fileName, // 檔名
'<h1>aicoder.com 全棧實習不8000就業不還實習費</h1>', // 寫入檔案內容
err => { // 寫入成功後的回撥函式
if (err) throw err;
console.log('檔案內容已經寫入!');
}
);
寫入大量資料到檔案
寫入大量檔案的方式,可以用流的方式,可以用管道的方式,使用基本類似。且看程式碼。
- 流的方式寫入檔案。
語法: fs.createWriteStream(path, [options])
引數:
- path 檔案路徑
- [options] flags:指定檔案操作,預設'w',;encoding,指定讀取流編碼;start指定寫入檔案的位置
const fs = require('fs');
const path = require('path');
// 把當前js所在的目錄中的a.html檔案的路徑賦值給 fileName
let fileName = path.join(__dirname, 'msg.log');
// 建立議檔案的可寫流。
let ws = fs.createWriteStream(fileName, { start: 0 });
ws.on('open', function(fd) {
console.log('要寫入的資料檔案已經開啟,檔案描述符是: ' + fd);
});
// 監聽寫入異常事件
ws.on('error', err => {
console.log(err);
});
// 監聽寫入完成的事件
ws.on('finish', () => {
console.log('寫入結束!');
});
// 寫入100個字串。
for (let i = 0; i < 100; i++) {
// write方法可以把內容寫入到可寫流中。
let w_flag = ws.write('aicoder.com 全棧實習 \r\n');
//當快取區寫滿時,輸出false
console.log(w_flag);
}
ws.end('結束寫入!');
- 大檔案的複製(綜合運用檔案流)
大檔案複製,可以用一個可讀流和一個可寫流進行復制,由於讀取檔案一般比寫入檔案要快。所以要避免緩衝區不可寫入的時候,暫停檔案流的讀取,可以繼續寫入的時候,再繼續讀取資料。
const fs = require('fs');
const path = require('path');
let fileNameSrc = path.join(__dirname, 'jdk.dmg'); // 複製的原始檔
let fileNameDist = path.join(__dirname, 'jdk1.dmg'); // 拷貝完的目標檔名
// 建立可讀流
let rs = fs.createReadStream(fileNameSrc, { start: 0 });
// 建立可寫流
let ws = fs.createWriteStream(fileNameDist, { start: 0 });
rs.on('data', function(chunk) {
if (ws.write(chunk) === false) {
// 判斷資料流是否已經寫入目標了
rs.pause();
}
});
// 當可讀流結束的時候,讓可寫流結束。
rs.on('end', function() {
ws.end(); // 結束可寫流
console.log('檔案複製成功');
});
ws.on('drain', function() {
rs.resume(); // 繼續啟動讀取資料流
});
- 使用管道的方式大檔案的複製。
const fs = require('fs');
const path = require('path');
let fileNameSrc = path.join(__dirname, 'jdk.dmg'); // 複製的原始檔
let fileNameDist = path.join(__dirname, 'jdk1.dmg'); // 拷貝完的目標檔名
let rs = fs.createReadStream(fileNameSrc, { start: 0 });
let ws = fs.createWriteStream(fileNameDist, { start: 0 });
rs.on('end', () => {
console.log('讀取完畢');
});
ws.on('finish', () => {
console.log('寫入成功!');
});
// 可讀流直接跟可寫流建立管道。可讀流的資料 直接通過管道流向 可寫流
rs.pipe(ws);
/*******************
|----------|
|----------|
|---rs ---|-----------\
|----------| |
|__________| |
|
| | |
| | |
|----------|
| ws |
|__________|
********************/
檔案相關的其他操作
重新命名
語法:fs.rename(oldPath, newPath, callback);
引數:
- oldPath, 原目錄/檔案的完整路徑及名;
- newPath, 新目錄/檔案的完整路徑及名;如果新路徑與原路徑相同,而只檔名不同,則是重新命名
- [callback(err)], 操作完成回撥函式;err操作失敗物件
fs.rename(__dirname + '/test', __dirname + '/fsDir', function (err) {
if(err) {
console.error(err);
return;
}
console.log('重新命名成功')
});
修改檔案或目錄的操作許可權
語法:fs.utimes(path, mode, callback);
引數:
- path, 要檢視目錄/檔案的完整路徑及名;
- mode, 指定許可權,如:0666 8進位制,許可權:所有使用者可讀、寫,
- [callback(err)], 操作完成回撥函式;err操作失敗物件
fs.chmod(__dirname + '/fsDir', 0666, function (err) {
if(err) {
console.error(err);
return;
}
console.log('修改許可權成功')
});
檢視檔案與目錄的是否存在
語法:fs.exists(path, callback);
引數:
- path, 要檢視目錄/檔案的完整路徑及名;
- [callback(exists)], 操作完成回撥函式;exists true存在,false表示不存在
fs.exists(__dirname + '/te', function (exists) {
var retTxt = exists ? retTxt = '檔案存在' : '檔案不存在';
console.log(retTxt);
});
資料夾讀取操作
const fs = require('fs');
const path = require('path');
// 讀取資料夾
fs.readdir(__dirname, function(err, files) {
if (err) {
console.error(err);
return;
}
// 對資料夾中的所有路徑做處理
files.forEach(file => {
let filePath = path.join(__dirname, file);
// 讀取檔案的資訊,判斷是檔案還是路徑
fs.stat(filePath, function(err, stat) {
if (err) throw err;
console.log(filePath, stat.isFile() ? ' is: file' : ' is: dir');
});
});
});
後續的學習
後續,可以實現一下自定義的可寫流、可讀流,多運用一些管道的方法,多看一下官方的文件,相信您已經可以掌握了檔案模組相關的內容。
參考: