Node
的文件處理涉及到前面說(shuō)的ptah
模塊,以及fs
文件系統(tǒng)死遭、stream
流處理、Buffer
緩沖器等模塊凯旋。內(nèi)容可能比較多呀潭,相關(guān)內(nèi)容請(qǐng)以官網(wǎng)文檔為主,此處主要以案例講解為主瓦阐,分享給大家一些常用的經(jīng)典案例蜗侈。細(xì)節(jié)就不展開(kāi)了篷牌。
fs文件系統(tǒng)
fs
模塊提供了很多文件操作相關(guān)的api睡蟋,比如:監(jiān)控文件夾、文件枷颊,文件重命名戳杀,文件讀寫(xiě)该面,文件修改權(quán)限、文件讀寫(xiě)流等信卡。
在此隔缀,我們僅以幾個(gè)案例的方式來(lái)驅(qū)動(dòng)學(xué)習(xí)Node的文件系統(tǒng),細(xì)節(jié)請(qǐng)?jiān)敿?xì)閱讀Node的api文檔或者源碼傍菇。
案例:
如何監(jiān)控文件夾的變化猾瘸?
如何讀取一個(gè)文件?
如何把內(nèi)容寫(xiě)入另外一個(gè)文件丢习?
文件件讀取牵触、文件重命名、移動(dòng)等各種功能
如何監(jiān)控文件夾的變化咐低?
fs
模塊提供了FSWatcher
類(lèi)輔助我們進(jìn)行監(jiān)控文件夾揽思,可以通過(guò)fs.watch()
方法返回此類(lèi)型實(shí)例。然后通過(guò)注冊(cè)相關(guān)的事件回調(diào)函數(shù)達(dá)到對(duì)文件變化的監(jiān)控见擦。不廢話钉汗,直接上代碼,詳細(xì)內(nèi)容請(qǐng)參考官方文檔鲤屡。
// 引入fs模塊
const fs = require('fs');
// 通過(guò)fs.watch方法可以創(chuàng)建一個(gè)fs.FSWatcher類(lèi)的實(shí)例损痰。
let watcher = fs.watch(
__dirname, // 監(jiān)控的文件夾,我這里用了一個(gè)模塊的變量酒来,當(dāng)前js文件所在的目錄
{ recursive: true }, // 是否監(jiān)控子文件夾徐钠,還可以設(shè)置編碼,具體參考官網(wǎng)文檔
(eventName, fileName) => { // 回調(diào)函數(shù)接受兩個(gè)參數(shù):事件名字和文件名
console.log(`事件名字:${eventName}, 文件名字: ${fileName}`);
}
);
// 還可以單獨(dú)注冊(cè)事件役首,回調(diào)函數(shù)跟watch方法一致尝丐。還可以監(jiān)聽(tīng):error事件。
watcher.on('change', (eventType, fileName) => {
console.log('事件名:%s , 文件名: %s', eventType, fileName);
});
// 設(shè)置13秒中后衡奥,退出監(jiān)控文件夾
setTimeout(() => {
// 關(guān)閉監(jiān)控爹袁。
watcher.close(function(err) {
if (err) {
console.error(err);
}
console.log('關(guān)閉watch');
});
}, 13000);
/********
以下是輸出的結(jié)果:當(dāng)js文件修改的時(shí)候
事件名字:change, 文件名字: 05filewatch.js
事件名:change , 文件名: 05filewatch.js
事件名字:change, 文件名字: 05filewatch.js
事件名:change , 文件名: 05filewatch.js
*/
如何讀取一個(gè)文件?
讀取文件可以分為小文件讀取和大文件矮固,如果小文件可以一次性的把文件內(nèi)容讀取出來(lái)然后再處理失息,對(duì)于比較大的文件用文件流處理。
一次性讀取小文件的方法
fs.readFile()
方法可以幫助我們一次性的把文件里面內(nèi)容讀取出來(lái)档址。文檔
const fs = require('fs');
const path = require('path');
// 把當(dāng)前js所在的目錄中的a.html文件的路徑賦值給 fileName
let fileName = path.join(__dirname, 'a.html');
// 讀取a.html文件盹兢,按照utf8的編碼方式讀取∈厣欤回調(diào)函數(shù)第一個(gè)參數(shù)是err(這個(gè)是一個(gè)默認(rèn)的約定規(guī)范绎秒,大多數(shù)node
// 的回調(diào)函數(shù)第一個(gè)參數(shù)都是異常的err,如果為空則表示沒(méi)有錯(cuò)誤尼摹。)第二個(gè)參數(shù)是文件的所有內(nèi)容见芹。
fs.readFile(fileName, { encoding: 'utf8' }, (err, data) => {
if (err) throw err; // 判斷是否讀取錯(cuò)誤
console.log(data); // 文件內(nèi)容讀取并打印到控制臺(tái)剂娄。
});
//readFileSync方法是readFile的同步版本,沒(méi)有回調(diào)函數(shù)玄呛,函數(shù)的返回值就是文件內(nèi)容阅懦。
// 以下代碼是同步讀取,不使用回調(diào)函數(shù)徘铝,此方法不是用的: libuv 的線程池的線程執(zhí)行耳胎,所以慎用!惕它!
let fileContent = fs.readFileSync(fileName, { encoding: 'utf8' });
console.log(fileContent);
注意:此方法只適合比較小的文件场晶,不適合大文件讀取。
同步方法盡量少用怠缸,異步的讀取文件都是利用了libuv 的線程池的線程讀取文件诗轻,所以讀取文件等待期間不會(huì)阻塞主線程的事件循環(huán)。
讀取大文件
使用stream讀取大文件揭北。當(dāng)然你可以自定義可讀流扳炬,也可以用node內(nèi)置的創(chuàng)建可讀流的api。
const fs = require('fs');
const path = require('path');
// 把當(dāng)前js所在的目錄中的a.html文件的路徑賦值給 fileName
let fileName = path.join(__dirname, 'a.html');
// 創(chuàng)建可讀流
let readStream = fs.createReadStream(fileName, {
flags: 'r', // 設(shè)置文件只讀模式打開(kāi)文件
encoding: 'utf8' // 設(shè)置讀取文件的內(nèi)容的編碼
});
// 打開(kāi)文件流的事件搔体。
readStream.on('open', fd => {
console.log('文件可讀流已打開(kāi)恨樟,句柄:%s', fd);
});
// 可讀流打開(kāi)后,會(huì)源源不斷的觸發(fā)此事件方法疚俱,回調(diào)函數(shù)參數(shù)就是讀取的數(shù)據(jù)劝术。
readStream.on('data', data => {
console.log(data);
});
readStream.on('close', () => {
console.log('文件可讀流結(jié)束!');
});
如何寫(xiě)入文件
寫(xiě)入文件也是一樣的呆奕,如果是比較少的內(nèi)容可以一次性寫(xiě)入文件养晋。其他情況可以用流、管道等方式解決梁钾。
一次性寫(xiě)入少量?jī)?nèi)容到文件
const fs = require('fs');
const path = require('path');
// 把當(dāng)前js所在的目錄中的a.html文件的路徑賦值給 fileName
let fileName = path.join(__dirname, 'a.html');
// 寫(xiě)入文件
fs.writeFile(
fileName, // 文件名
'<h1>aicoder.com 全棧實(shí)習(xí)不8000就業(yè)不還實(shí)習(xí)費(fèi)</h1>', // 寫(xiě)入文件內(nèi)容
err => { // 寫(xiě)入成功后的回調(diào)函數(shù)
if (err) throw err;
console.log('文件內(nèi)容已經(jīng)寫(xiě)入绳泉!');
}
);
寫(xiě)入大量數(shù)據(jù)到文件
寫(xiě)入大量文件的方式,可以用流的方式姆泻,可以用管道的方式零酪,使用基本類(lèi)似。且看代碼拇勃。
- 流的方式寫(xiě)入文件四苇。
語(yǔ)法: fs.createWriteStream(path, [options])
參數(shù):
- path 文件路徑
- [options] flags:指定文件操作,默認(rèn)'w',方咆;encoding,指定讀取流編碼月腋;start指定寫(xiě)入文件的位置
const fs = require('fs');
const path = require('path');
// 把當(dāng)前js所在的目錄中的a.html文件的路徑賦值給 fileName
let fileName = path.join(__dirname, 'msg.log');
// 創(chuàng)建議文件的可寫(xiě)流。
let ws = fs.createWriteStream(fileName, { start: 0 });
ws.on('open', function(fd) {
console.log('要寫(xiě)入的數(shù)據(jù)文件已經(jīng)打開(kāi),文件描述符是: ' + fd);
});
// 監(jiān)聽(tīng)寫(xiě)入異常事件
ws.on('error', err => {
console.log(err);
});
// 監(jiān)聽(tīng)寫(xiě)入完成的事件
ws.on('finish', () => {
console.log('寫(xiě)入結(jié)束罗售!');
});
// 寫(xiě)入100個(gè)字符串辜窑。
for (let i = 0; i < 100; i++) {
// write方法可以把內(nèi)容寫(xiě)入到可寫(xiě)流中钩述。
let w_flag = ws.write('aicoder.com 全棧實(shí)習(xí) \r\n');
//當(dāng)緩存區(qū)寫(xiě)滿(mǎn)時(shí)寨躁,輸出false
console.log(w_flag);
}
ws.end('結(jié)束寫(xiě)入!');
- 大文件的復(fù)制(綜合運(yùn)用文件流)
大文件復(fù)制牙勘,可以用一個(gè)可讀流和一個(gè)可寫(xiě)流進(jìn)行復(fù)制职恳,由于讀取文件一般比寫(xiě)入文件要快。所以要避免緩沖區(qū)不可寫(xiě)入的時(shí)候方面,暫停文件流的讀取放钦,可以繼續(xù)寫(xiě)入的時(shí)候,再繼續(xù)讀取數(shù)據(jù)恭金。
const fs = require('fs');
const path = require('path');
let fileNameSrc = path.join(__dirname, 'jdk.dmg'); // 復(fù)制的源文件
let fileNameDist = path.join(__dirname, 'jdk1.dmg'); // 拷貝完的目標(biāo)文件名
// 創(chuàng)建可讀流
let rs = fs.createReadStream(fileNameSrc, { start: 0 });
// 創(chuàng)建可寫(xiě)流
let ws = fs.createWriteStream(fileNameDist, { start: 0 });
rs.on('data', function(chunk) {
if (ws.write(chunk) === false) {
// 判斷數(shù)據(jù)流是否已經(jīng)寫(xiě)入目標(biāo)了
rs.pause();
}
});
// 當(dāng)可讀流結(jié)束的時(shí)候操禀,讓可寫(xiě)流結(jié)束。
rs.on('end', function() {
ws.end(); // 結(jié)束可寫(xiě)流
console.log('文件復(fù)制成功');
});
ws.on('drain', function() {
rs.resume(); // 繼續(xù)啟動(dòng)讀取數(shù)據(jù)流
});
- 使用管道的方式大文件的復(fù)制横腿。
const fs = require('fs');
const path = require('path');
let fileNameSrc = path.join(__dirname, 'jdk.dmg'); // 復(fù)制的源文件
let fileNameDist = path.join(__dirname, 'jdk1.dmg'); // 拷貝完的目標(biāo)文件名
let rs = fs.createReadStream(fileNameSrc, { start: 0 });
let ws = fs.createWriteStream(fileNameDist, { start: 0 });
rs.on('end', () => {
console.log('讀取完畢');
});
ws.on('finish', () => {
console.log('寫(xiě)入成功颓屑!');
});
// 可讀流直接跟可寫(xiě)流建立管道」⒑福可讀流的數(shù)據(jù) 直接通過(guò)管道流向 可寫(xiě)流
rs.pipe(ws);
/*******************
|----------|
|----------|
|---rs ---|-----------\
|----------| |
|__________| |
|
| | |
| | |
|----------|
| ws |
|__________|
********************/
文件相關(guān)的其他操作
重命名
語(yǔ)法:fs.rename(oldPath, newPath, callback);
參數(shù):
- oldPath, 原目錄/文件的完整路徑及名揪惦;
- newPath, 新目錄/文件的完整路徑及名;如果新路徑與原路徑相同罗侯,而只文件名不同器腋,則是重命名
- [callback(err)], 操作完成回調(diào)函數(shù);err操作失敗對(duì)象
fs.rename(__dirname + '/test', __dirname + '/fsDir', function (err) {
if(err) {
console.error(err);
return;
}
console.log('重命名成功')
});
修改文件或目錄的操作權(quán)限
語(yǔ)法:fs.utimes(path, mode, callback);
參數(shù):
- path, 要查看目錄/文件的完整路徑及名钩杰;
- mode, 指定權(quán)限纫塌,如:0666 8進(jìn)制,權(quán)限:所有用戶(hù)可讀讲弄、寫(xiě)护戳,
- [callback(err)], 操作完成回調(diào)函數(shù);err操作失敗對(duì)象
fs.chmod(__dirname + '/fsDir', 0666, function (err) {
if(err) {
console.error(err);
return;
}
console.log('修改權(quán)限成功')
});
查看文件與目錄的是否存在
語(yǔ)法:fs.exists(path, callback);
參數(shù):
- path, 要查看目錄/文件的完整路徑及名垂睬;
- [callback(exists)], 操作完成回調(diào)函數(shù)媳荒;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;
}
// 對(duì)文件夾中的所有路徑做處理
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');
});
});
});
后續(xù)的學(xué)習(xí)
后續(xù)钳枕,可以實(shí)現(xiàn)一下自定義的可寫(xiě)流、可讀流赏壹,多運(yùn)用一些管道的方法鱼炒,多看一下官方的文檔,相信您已經(jīng)可以掌握了文件模塊相關(guān)的內(nèi)容蝌借。
參考: