node.js同步/異步文件讀寫-fs,Stream文件流操作實(shí)例詳解
文件讀寫——fs
nodejs文檔里api一大堆,該怎么選擇呢?我這里挑選了幾個(gè)常用的出來。
案例說明:當(dāng)前目錄下有index.js和1.txt兩個(gè)文件

fs 模塊提供了一些 API,用于以一種類似標(biāo)準(zhǔn) POSIX 函數(shù)的方式與文件系統(tǒng)進(jìn)行交互。
const fs = require('fs');
- 異步形式的最后一個(gè)參數(shù)都是完成時(shí)回調(diào)函數(shù)。 傳給回調(diào)函數(shù)的參數(shù)取決于具體方法,但回調(diào)函數(shù)的第一個(gè)參數(shù)都會(huì)保留給異常。 如果操作成功完成,則第一個(gè)參數(shù)會(huì)是 null 或 undefined。
const fs = require('fs');
fs.unlink('/tmp/hello', (err) => {
if (err) throw err;
console.log('成功刪除 /tmp/hello');
});
- 當(dāng)使用同步操作時(shí),任何異常都會(huì)被立即拋出,可以使用 try/catch 來處理異常,或讓異常向上冒泡。
const fs = require('fs');
try {
fs.unlinkSync('/tmp/hello');
console.log('successfully deleted /tmp/hello');
} catch (err) {
// handle the error
}
異步的文件讀取
- 我們先讀取一下這個(gè)文件:
const fs = require("fs");
fs.readFile("./1.txt",(err,data)=>{
console.log(data);
})

我們?cè)趺粗浪_讀取到了呢?一堆16進(jìn)制的buffer.
readFile接收三個(gè)參數(shù),必選參數(shù):路徑,可選參數(shù):編碼,必選參數(shù):回調(diào)函數(shù)。
const fs = require("fs");
fs.readFile("./1.txt","utf-8",(err,data)=>{
console.log(data);
})

- 注意兩個(gè)異步不能相干:
const fs = require("fs");
const fn = async () => {
const data = await new Promise((resovle,reject) => {
fs.readFile('1.txt','utf-8',(err,data) => {
if(err) return reject(err)
resovle(data)
})
})
console.log(data,111);
}
console.log(1);
console.log(fn(),222);
console.log(2);

顯然異步讀取數(shù)據(jù)不能這樣做,人家本身就是異步的,你還拿異步的去取,肯定靠天吃飯啦。
想要準(zhǔn)確的拿數(shù)據(jù),還是得遵守async函數(shù)的規(guī)則,乖乖在它的懷抱下等待。
同步的文件讀取
- fs.readFileSync(‘路徑’,‘編碼’)
const fs = require("fs");
console.log(1);
const data = fs.readFileSync('1.txt','utf-8')
console.log(data);
console.log(2);

很愉快的讀取到數(shù)據(jù),因?yàn)槭峭剑院竺娴拇a必須等待前面讀取文件成功才能繼續(xù)執(zhí)行。如果文件非常大,所以還是乖乖異步吧!
異步的寫文件
fs.writeFile(file, data[, options])
- fs.writeFile,第一個(gè)參數(shù)為路徑和文件,第二個(gè)參數(shù)為寫入的數(shù)據(jù),第三個(gè)會(huì)回執(zhí)消息,如果沒有錯(cuò)誤會(huì)返回null:
如果你把1.txt刪了或者路徑下沒有這個(gè)文件的話會(huì)自動(dòng)創(chuàng)建文件
const fs = require("fs");
console.log(1);
const data = '寫入測試文字'
fs.writeFile('1.txt',data,res=>{
console.log(res,11111);
})
console.log(2);

但是如果僅是這樣的話,會(huì)把原來的文字覆蓋。
同步的寫文件
fs.writeFileSync(file, data[, options])
const fs = require("fs");
console.log(1);
const data = '寫入測試文字'
fs.writeFileSync('1.txt', data, res => {
console.log(res, 11111);
})
console.log(2);

因?yàn)檫@個(gè)寫函數(shù)如果沒有對(duì)應(yīng)文件會(huì)自己新建,所以想報(bào)錯(cuò)還真的很難!暫時(shí)沒有錯(cuò)誤示例!
open,打開文件
fs.open(path, flags[, mode], callback)
四個(gè)參數(shù),mode是默認(rèn)的:
- path
<string>|<Buffer>|<URL> - flags
<string>|<number> - mode
<integer>Default: 0o666 - callback
<Function>- err
<Error> - fd
<integer>
- err
具體的參數(shù):
flags 可以是:
‘r’ - 以讀取模式打開文件。如果文件不存在則發(fā)生異常。
‘r+’ - 以讀寫模式打開文件。如果文件不存在則發(fā)生異常。
‘rs+’ - 以同步讀寫模式打開文件。命令操作系統(tǒng)繞過本地文件系統(tǒng)緩存。
這對(duì) NFS 掛載模式下打開文件很有用,因?yàn)樗梢宰屇闾^潛在的舊本地緩存。 它對(duì) I/O 的性能有明顯的影響,所以除非需要,否則不要使用此標(biāo)志。
注意,這不會(huì)使 fs.open() 進(jìn)入同步阻塞調(diào)用。 如果那是你想要的,則應(yīng)該使用 fs.openSync()。
‘w’ - 以寫入模式打開文件。文件會(huì)被創(chuàng)建(如果文件不存在)或截?cái)啵ㄈ绻募嬖冢?/p>
‘wx’ - 類似 ‘w’,但如果 path 存在,則失敗。
‘w+’ - 以讀寫模式打開文件。文件會(huì)被創(chuàng)建(如果文件不存在)或截?cái)啵ㄈ绻募嬖冢?/p>
‘wx+’ - 類似 ‘w+’,但如果 path 存在,則失敗。
‘a’ - 以追加模式打開文件。如果文件不存在,則會(huì)被創(chuàng)建。
‘ax’ - 類似于 ‘a’,但如果 path 存在,則失敗。
‘a+’ - 以讀取和追加模式打開文件。如果文件不存在,則會(huì)被創(chuàng)建。
‘ax+’ - 類似于 ‘a+’,但如果 path 存在,則失敗。
mode 可設(shè)置文件模式(權(quán)限和 sticky 位),但只有當(dāng)文件被創(chuàng)建時(shí)才有效。默認(rèn)為 0o666,可讀寫。
- 該回調(diào)有兩個(gè)參數(shù) (err, fd)。
特有的標(biāo)志 ‘x’(在 open(2) 中的 O_EXCL 標(biāo)志)確保 path 是新創(chuàng)建的。 在 POSIX 操作系統(tǒng)中,path 會(huì)被視為存在,即使是一個(gè)鏈接到一個(gè)不存在的文件的符號(hào)。 該特有的標(biāo)志有可能在網(wǎng)絡(luò)文件系統(tǒng)中無法使用。
flags 也可以是一個(gè)數(shù)字,[open(2)] 文檔中有描述; 常用的常量可從 fs.constants 獲取。 在 Windows 系統(tǒng)中,標(biāo)志會(huì)被轉(zhuǎn)換為與它等同的替代者,例如,O_WRONLY 轉(zhuǎn)換為 FILE_GENERIC_WRITE、或 O_EXCL|O_CREAT 轉(zhuǎn)換為 CREATE_NEW,通過 CreateFileW 接受。
在 Linux 中,當(dāng)文件以追加模式打開時(shí),定位的寫入不起作用。 內(nèi)核會(huì)忽略位置參數(shù),并總是附加數(shù)據(jù)到文件的末尾。
callback[err,fa]
失敗就不說了,讀取成功后,fd將是讀取文件中read方法的第一個(gè)參數(shù),這個(gè)參數(shù)代表指定的文件。
注意:fs.open() 某些標(biāo)志的行為是與平臺(tái)相關(guān)的。 因此,在 macOS 和 Linux 下用 ‘a+’ 標(biāo)志打開一個(gè)目錄(見下面的例子),會(huì)返回一個(gè)錯(cuò)誤。 與此相反,在 Windows 和 FreeBSD,則會(huì)返回一個(gè)文件描述符。
// macOS 與 Linux
fs.open('<directory>', 'a+', (err, fd) => {
// => [Error: EISDIR: illegal operation on a directory, open <directory>]
});
// Windows 與 FreeBSD
fs.open('<directory>', 'a+', (err, fd) => {
// => null, <fd>
});
fs.read讀文件,基于open
參數(shù)
- fd
<integer> - buffer
<Buffer>|<Uint8Array> - offset
<integer> - length
<integer> - position
<integer> - callback
<Function>- err
<Error> - bytesRead
<integer> - buffer
<Buffer>
- err
參數(shù)的值
從 fd 指定的文件中讀取數(shù)據(jù)。
buffer 是數(shù)據(jù)將被寫入到的 buffer。
offset 是 buffer 中開始寫入的偏移量。
length 是一個(gè)整數(shù),指定要讀取的字節(jié)數(shù)。
position 指定從文件中開始讀取的位置。 如果 position 為 null,則數(shù)據(jù)從當(dāng)前文件讀取位置開始讀取,且文件讀取位置會(huì)被更新。 如果 position 為一個(gè)整數(shù),則文件讀取位置保持不變。
回調(diào)有三個(gè)參數(shù) (err, bytesRead, buffer)。
如果調(diào)用該方法的 util.promisify() 版本,將會(huì)返回一個(gè)包含 bytesRead 和 buffer 屬性的 Promise。
例子:
- 使用read要和open結(jié)合在一起,通過open返回的fd指定文件拿到要讀取的目標(biāo)!
- 注意nodejs的buffer已經(jīng)廢棄了很多api!
var fs = require('fs');
fs.open('./1.txt', 'r', function (err, fd) {
if(err) throw err
//buf根據(jù)你的需要設(shè)定每次的讀取長度
var buf = new Buffer.alloc(225);
//讀取fd文件內(nèi)容到buf緩存區(qū),如果position設(shè)置為null就會(huì)從最開始的地方開始讀
fs.read(fd, buf, 0, 20, null, function (err, bytesRead, buffer) {
console.log(buf.slice(0, bytesRead).toString());
console.log(buf);
});
});

fs.write寫文件,基于open
參數(shù)
- fd
<integer> - buffer
<Buffer>|<Uint8Array> - offset
<integer> - length
<integer> - position
<integer> - callback
<Function>- err
<Error> - bytesWritten
<integer> - buffer
<Uint8Array>
- err
參數(shù)的值
寫入 buffer 到 fd 指定的文件。
offset 決定 buffer 中被寫入的部分,length 是一個(gè)整數(shù),指定要寫入的字節(jié)數(shù)。
position 指向從文件開始寫入數(shù)據(jù)的位置的偏移量。 如果 typeof position !== ‘number’,則數(shù)據(jù)從當(dāng)前位置寫入。詳見 pwrite(2)。
回調(diào)有三個(gè)參數(shù) (err, bytesWritten, buffer),其中 bytesWritten 指定從 buffer 寫入了多少字節(jié)。
如果以 util.promisify() 的形式調(diào)用該方法,則會(huì)返回包含 bytesWritten 和 buffer 屬性的 Promise 的對(duì)象。
注意,多次對(duì)同一文件使用 fs.write 且不等待回調(diào),是不安全的。 對(duì)于這種情況,強(qiáng)烈推薦使用 fs.createWriteStream。
在 Linux 上,當(dāng)文件以追加模式打開時(shí),指定位置的寫入是不起作用的。 內(nèi)核會(huì)忽略位置參數(shù),并總是將數(shù)據(jù)追加到文件的末尾。
例子
var fs = require('fs');
fs.open('./1.txt', 'r+', function (err, fd) {
if(err) throw err
fs.write(fd, '文件追加字符測試', 20, 'utf-8', (err, written, buffer)=>{
console.log(err);
console.log(buffer);
})
});
寫入前,別眨眼:
寫入后的結(jié)果,寫入位置20,打印err,空,打印buffer,剛才寫入的字符串!

- 提升:位置和數(shù)據(jù)的寫入和讀取就是各位通過邏輯來控制的了!
文件流Stream
- 想象一下,如果把文件讀取比作向一個(gè)池子里抽水,同步會(huì)阻塞程序,異步會(huì)等待結(jié)果,如果這個(gè)池子特別大怎么辦?有三峽水庫那么大怎么辦?你要等到多久才能喝到抽的水?
- 因此便會(huì)有了文件流,文件流就好比你一邊抽一邊取,不用等池子滿了再用一樣方便。
- 因?yàn)榱髟谖募x寫里非常抽象,所以并不能明顯確定,只能勉強(qiáng)通過一些特征表示;
- fs繼承于Stream;
讀取流例子
const fs = require("fs");
console.log(111111);
const read = fs.createReadStream('1.txt')
read.setEncoding('utf-8')
read.resume();//讓文件流開始'流'動(dòng)起來
read.on('data',data =>{//監(jiān)聽讀取的數(shù)據(jù),如果打印data就是文件的內(nèi)容
console.log('正在讀');
})
read.on('end', () => { //監(jiān)聽狀態(tài)
console.log('文件讀取結(jié)束');
})
console.log(222222);
我這里準(zhǔn)備了2萬+行的數(shù)據(jù),監(jiān)聽的data打印了多少次就說明被流監(jiān)聽了多少次,期間就可以使用多少次這個(gè)數(shù)據(jù),用于一些超大型的數(shù)據(jù)讀取還是非常有效的。

寫入流
const fs = require("fs");
console.log(111111);
const read = fs.createReadStream('1.txt')
read.setEncoding('utf-8')
read.resume();//讓文件流開始'流'動(dòng)起來
read.on('data',data =>{
console.log('正在讀');
})
read.on('end', () => { //監(jiān)聽狀態(tài)
console.log('文件讀取結(jié)束');
})
console.log(222222);
console.log(333333);
const write = fs.createWriteStream('2.txt')
read.pipe(write) //pipe就是那根水管,抽向2.txt
console.log(444444);如果你自己測試的時(shí)候看著2.txt的行數(shù)在飛漲,會(huì)更加直觀!

- Node.js中的流(Stream)的作用詳解
- Node.js數(shù)據(jù)流Stream之Duplex流和Transform流用法
- Node.js數(shù)據(jù)流Stream之Readable流和Writable流用法
- node.js中stream流中可讀流和可寫流的實(shí)現(xiàn)與使用方法實(shí)例分析
- node.js使用stream模塊實(shí)現(xiàn)自定義流示例
- Node.js中你不可不精的Stream(流)
- Node.js中流(stream)的使用方法示例
- Node.js中的流(Stream)介紹
- Node.js 中的流Stream模塊簡介及如何使用流進(jìn)行數(shù)據(jù)處理
相關(guān)文章
Node.js實(shí)現(xiàn)大文件斷點(diǎn)續(xù)傳示例詳解
這篇文章主要為大家介紹了Node.js實(shí)現(xiàn)大文件斷點(diǎn)續(xù)傳示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
實(shí)戰(zhàn)node靜態(tài)文件服務(wù)器的示例代碼
本篇文章主要介紹了實(shí)戰(zhàn)node靜態(tài)文件服務(wù)器的示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-03-03
node.js中module.exports與exports用法上的區(qū)別
Node.js 引入了模塊(Module)概念,一個(gè)模塊可以通過module.exports 或 exports 將函數(shù)、變量等導(dǎo)出,以使其它 JavaScript 腳本通過require() 函數(shù)引入并使用。那么node.js中module.exports與exports有什么區(qū)別呢?下面小編給大家解答下2016-09-09
使用Nodejs開發(fā)微信公眾號(hào)后臺(tái)服務(wù)實(shí)例
這篇文章主要介紹了使用Nodejs開發(fā)微信公眾號(hào)后臺(tái)服務(wù)實(shí)例,在這個(gè)實(shí)例中,主要使用到了express, wechat, mongodb, monk等模塊,需要的朋友可以參考下2014-09-09
為nuxt項(xiàng)目寫一個(gè)面包屑cli工具實(shí)現(xiàn)自動(dòng)生成頁面與面包屑配置
這篇文章主要介紹了為nuxt項(xiàng)目寫一個(gè)面包屑cli工具實(shí)現(xiàn)自動(dòng)生成頁面與面包屑配置,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09
Nodejs-cluster模塊知識(shí)點(diǎn)總結(jié)及實(shí)例用法
在本篇文章里小編給大家整理的是一篇關(guān)于Nodejs-cluster模塊知識(shí)點(diǎn)總結(jié)及實(shí)例用法,有興趣的朋友們可以跟著學(xué)習(xí)下。2021-12-12
Node.js中多進(jìn)程模塊Cluster的介紹與使用
眾所周知Node.js是單線程的,一個(gè)單獨(dú)的Node.js進(jìn)程無法充分利用多核。Node.js從v0.6.0開始,新增cluster模塊,讓Node.js開發(fā)Web服務(wù)時(shí),很方便的做到充分利用多核機(jī)器。這篇文章主要給大家介紹了關(guān)于Node.js中多進(jìn)程模塊Cluster的相關(guān)資料,需要的朋友可以參考下2017-05-05
使用Nodejs連接mongodb數(shù)據(jù)庫的實(shí)現(xiàn)代碼
這篇文章主要介紹了使用Nodejs連接mongodb數(shù)據(jù)庫的實(shí)現(xiàn)代碼,需要的朋友可以參考下2017-08-08

