nodejs中如何使用流數據讀寫文件

在nodejs中,可以使用fs模塊的readFile方法、readFileSync方法、read方法和readSync方法讀取一個文件的內容,還可以使用fs模塊的writeFile方法、writeFileSync方法、write方法和writeSync方法向一個文件中寫入內容。

它們各自的區別如下:

在使用readFile、readFileSync讀文件或writeFile、writeFileSync寫文件時,nodejs會將該文件內容視爲一個整體,爲其分配緩存區並一次性將內容讀取到緩存區中,在這期間,nodejs將不能執行任何其他處理。

在使用read、readSync讀文件時,nodejs將不斷地將文件中一小塊內容讀入緩存區,最後從該緩存區中讀取文件內容。使用rite、writeSync寫文件時,nodejs執行如下過程:1、將需要書寫的數據寫到一個內存緩衝區;2、待緩衝區寫滿之後再將該緩衝區內容寫入文件中;3、重複執行過程1和過程2,直到數據全部寫入文件爲止。所以用這4種方法在讀寫文件時,nodejs可以執行其他處理。

但在很多時候,並不關心整個文件的內容,而只關注是否從文件中讀取到某些數據,以及在讀取到這些數據時所需執行的處理,此時可以使用nodejs中的文件流來執行。

所謂的"流":在應用程序中,流是一組有序的、有起點和終點的字節數據的傳輸手段。在應用程序中各種對象之間交換和傳輸數據時,總是先將該對象中所包含的數據轉換成各種形式的流數據(即字節數據),再通過流的傳輸,到達目的對象後再將流數據轉換爲該對象中可以使用的數據。

nodejs中使用實現了stream.Readable接口的對象來將對象數據讀取爲流數據,所有這些對象都是繼承了EventEmitter類的實例對象,在讀取數據的過程中,會觸發各種事件。

這些實現了stream.Readable接口的對象有:

  • fs模塊專用於將文件數據讀成流數據的fs.ReadStream方法
  • 代表客戶端請求對象的http.IncommingMessage對象,這個在前面http模塊方面文章中經常用到,http.createServer( function( req, res ){} )中的req對象就是典型的http.IncommingMessage對象
  • net.Socket對象,即一個socket端口對象
  • child.stdout對象,用於創建子進程的標準輸出流
  • child.stderr對象,用於創建子進程的標準錯誤輸出流
  • process.stdin對象,用於創建進程的標準輸入流
  • Gzip/Deflate/DeflateRaw對象,用於實現數據壓縮

以上這些實現了stream.Readable接口的對象可能會觸發的事件有:

  • readable事件,當可以從流中讀出數據時觸發
  • data事件,當讀取到來自文件、客戶端、服務器端等對象的新的數據時觸發,常見的有創建服務器監聽客戶端請求數據時的req.on( "data", function( dataChunk ){} )
  • end事件,當讀取完所有數據時觸發,此時data事件將不再會觸發
  • error事件,當讀取數據過程中產生錯誤時觸發
  • close事件,當關閉用於讀取數據流的對象時觸發。

實現了stream.Readable接口的對象具有如下方法:

  • read方法,用於讀取數據
  • setEncoding方法,用於指定用什麼編碼方式讀取數據
  • pause方法,用於通知對象停止觸發data事件
  • resume方法,用於通知對象恢復觸發data事件
  • pipe方法,用於設置一個數據通道,然後取出所有流數據並將其輸出到通道另一端所指向的目標對象中
  • unpipe方法,用於取消在pipe方法中設置的通道
  • unshift方法,當對流數據綁定一個解析器時,可以使用該方法取消該解析器的綁定,使用流數據可以使用其他方式解析

用於寫入數據的實現了stream.Readable接口的對象和讀取數據的相應對象差不多,常見的有:

  • fs.WriteSteam對象,用於寫入文件
  • http.ClientRequest對象,用於寫入HTTP客戶端請求數據
  • http.ServerResponse對象,用於寫入HTTP服務器端響應數據

這些用於寫入流數據的對象可能會觸發的事件有:

  • drain事件,當用於寫入數據的write方法返回false時觸發,表示操作系統緩存區中的數據已全部輸出到目標對象中,可以繼續向操作系統緩存區中寫入數據
  • finish事件,當end方法被調用且數據全部被寫入操作系統緩存區時觸發
  • pipe事件,當用於讀取數據的對象的pipe方法被調用時觸發
  • unpipe事件,當用於讀取數據的對象的unpipe方法被調用時觸發
  • error事件,當寫入數據過程中產生錯誤時觸發

這些用於寫入流數據的對象的方法有:

  • write方法,用於寫入數據
  • end方法,當沒有數據再被寫入流中時調用該方法。這會迫使操作系統緩存區中的剩餘數據被立即寫入目標對象中,當該方法被調用時,將不能繼續在目標對象中寫入數據。

使用ReadStream對象讀文件 fs.createReadStream

使用ReadStream對象讀文件就是將文件數據讀成流數據,可以使用fs模塊中的fs.createReadStream( path, [options] )方法,其中path爲必指定參數,用於指定需要被讀取的文件的完整路徑及文件名。options參數值是一個對象,其中的屬性爲:

options = {
    flags: "r",     // 用於指定對該文件採用什麼操作,默認爲r
    encoding: null, // 用於指定用什麼編碼格式讀取文件,默認null,可指定屬性爲 utf8、base64、ascii
    autoClose: true,// 用於指定是否關閉在讀取文件時操作系統內部使用的文件描述符,默認爲true,當文件讀取完畢或讀取文件過程中產生錯誤時文件關閉
    start: --,      // 使用整數值來指定文件的開始讀取位置,單位爲字節數
    end: --         // 使用整數值來指定文件的結束位置,單位爲字節數
}

當文件被打開時,將觸發ReadStream對象的open事件,在該事件觸發時調用的回調函數可以使用一個參數,參數值是被打開文件的文件描述符(也即文件句柄fd)。

下面給個使用fs.createReadStream()方法打開文件並讀取數據流的demo:

const fs = require( "fs" );

// 創建一個將文件內容讀取爲流數據的ReadStream對象
let fileReadStream = fs.createReadStream( "./a1.txt", {encoding: "utf-8", start: 0, end: 24} );

// 打開文件,回調函數參數fd是打開文件時返回的文件描述符(文件句柄)
fileReadStream.on( "open", function ( fd ) {
    console.log( "文件被打開,文件句柄爲%d", fd );
} );

// 暫停文件讀取
fileReadStream.pause();

// 1秒後取消暫停,繼續讀取文件流
setTimeout( function () {
    fileReadStream.resume();
}, 2000 );

// 讀取到文件新的數據時觸發的事件,回調函數參數dataChunk爲存放了已讀到的數據的緩存區對象或一個字符串
fileReadStream.on( "data", function ( dataChunk ) {
    console.log( "讀取到數據:" );
    console.log( dataChunk );
} );

// 讀取完所有數據時觸發,此時將不會再觸發data事件
fileReadStream.on( "end", function () {
    console.log( "文件已經全部讀取完畢" );
} );

// 用於讀取數據流的對象被關閉時觸發
fileReadStream.on( "close", function () {
    console.log( "文件被關閉" );
} );

// 當讀取數據過程中產生錯誤時觸發
fileReadStream.on( "error", function ( err ) {
    console.log( "文件讀取失敗。" );
} )

使用ReadStream對象寫入文件 fs.createWriteStream

fs.createWriteStream( path, [options] )方法可以創建一個將流數據寫入文件的WriteSteam對象。該方法的參數說明如下(這裏採用新說明方式,參數options爲對象,直接在對象名邊列出對象屬性說明,屬性值爲該參數屬性的默認值,這屬於僞代碼,請勿寫入實際代碼中):

fs.createWriteStream( 
    path,               // 必寫,用於指定需要被寫入數據的文件的完整
    options{
        flags: "w",     // 用於指定對該文件採用什麼操作,默認爲 w
        encoding: null, // 用於指定用什麼編碼格式讀取文件,默認null,可指定屬性爲 utf8、base64、ascii
        start:          // 使用整數值來指定文件的開始寫入位置,單位爲字節數,如果要在文件追加寫入數據,需將flag屬性設爲 a
    }
)

當文件被打開時,將觸發WriteStream對象的open事件,在該事件觸發時調用的回調函數可以使用一個參數,參數值是被打開文件的文件描述符(也即文件句柄fd)。

WriteStream對象寫入的方法是write(),用於將流數據寫入到目標對象中。writeable.write( chunk, [encoding], [callback] ),chunk參數是一個buffer對象或一個字符串,用於指定要寫入的數據,當爲字符串時,可以使用encoding參數來指定以何種編碼格式寫入文件,可以使用callback參數來指定當數據被寫入完畢時所調用的回調函數,該回調中不使用任何參數。write方法返回一個布爾值,當操作系統緩存區中寫滿時爲false。

WriteStream對象的end()方法指在寫入文件的場合中,當沒有數據再被寫入時可調用,此時會將緩存區中剩餘數據立即寫入文件中。writeable.end( [chunk], [encoding], [callback] ),參數含義與write方法完全一樣,同樣的回調函數不使用任何參數。

WriteStream對象還有一個對象bytesWritten屬性,屬性值是當前已在文件中寫入數據的字節數。

下面給出一個根據fs.createWriteStream對象執行的WriteStream示例demo:

const fs = require( "fs" );

let file = fs.createReadStream( "./a1.txt", { encoding: "utf8", start: 0, end: 20 } );
let out = fs.createWriteStream( "./a2.txt", { encoding: "utf8", start: 0 } );

file.on( "data", function ( dataChunk ) {
    out.write( dataChunk, function () {
        console.log( "將數據傳入a2.txt文件中" );
    } )
} )

out.on( "open", function ( fd ) {
    console.log( "文件描述符爲%d的文件正在被打開,它將被寫入來自a1.txt中的數據。", fd );
} )

file.on( "end", function () {
    out.end( ",再見", function () {
        console.log( "文件全部寫入完畢" );
        console.log( "共寫入%d字節數據", out.bytesWritten );
    } )
} )

喜歡本文請掃下方二維碼,關注微信公衆號: 前端小二,查看更多我寫的文章哦,多謝支持。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章