Node入門(mén)教程(9)第七章:NodeJs的文件處理

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)容蝌借。


參考:

  1. node.js之fs模塊
  2. Node.js 文件系統(tǒng)fs模塊

老馬免費(fèi)視頻教程

返回教程列表首頁(yè)

github地址:https://github.com/malun666/aicoder_node

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末昔瞧,一起剝皮案震驚了整個(gè)濱河市指蚁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌自晰,老刑警劉巖凝化,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異酬荞,居然都是意外死亡搓劫,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)混巧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)枪向,“玉大人,你說(shuō)我怎么就攤上這事咧党∶鼗祝” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵傍衡,是天一觀的道長(zhǎng)深员。 經(jīng)常有香客問(wèn)我,道長(zhǎng)聪舒,這世上最難降的妖魔是什么辨液? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮箱残,結(jié)果婚禮上滔迈,老公的妹妹穿的比我還像新娘。我一直安慰自己被辑,他們只是感情好燎悍,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著盼理,像睡著了一般谈山。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宏怔,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天奏路,我揣著相機(jī)與錄音,去河邊找鬼臊诊。 笑死鸽粉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的抓艳。 我是一名探鬼主播触机,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了儡首?” 一聲冷哼從身側(cè)響起片任,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蔬胯,沒(méi)想到半個(gè)月后对供,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡笔宿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年犁钟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了棱诱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泼橘。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖迈勋,靈堂內(nèi)的尸體忽然破棺而出炬灭,到底是詐尸還是另有隱情,我是刑警寧澤靡菇,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布重归,位于F島的核電站,受9級(jí)特大地震影響厦凤,放射性物質(zhì)發(fā)生泄漏鼻吮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一较鼓、第九天 我趴在偏房一處隱蔽的房頂上張望椎木。 院中可真熱鬧,春花似錦博烂、人聲如沸香椎。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)昂芜。三九已至脯厨,卻和暖如春触徐,著一層夾襖步出監(jiān)牢的瞬間材部,已是汗流浹背照皆。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工伤疙, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悼吱,地道東北人慎框。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像舆绎,于是被迫代替她去往敵國(guó)和親鲤脏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容