Node.js Stream 串流核心概念:高效處理大數據

Stream (串流) 是 Node.js 處理高性能資料交換的殺手鐧。如果說 Buffer 是裝滿資料的水桶,那麼 Stream 就是連接水源與目的地的「水管」。它讓 Node.js 能夠以極小的記憶體開銷,處理超大型檔案或高流量的網路傳輸。

為什麼需要 Stream?

試想一個極端場景:你需要讀取並處裡一個 20 GB 的大檔案,但伺服器的記憶體只有 8 GB。

  • 傳統做法 (fs.readFile):Node.js 會嘗試把 20 GB 的內容一次性全部載入(讀入一個緩衝區)。
    • 結果:記憶體立即爆滿 (Out of Memory),導致程式崩潰。
  • Stream 做法:Node.js 會將檔案拆解成一小塊一小塊 (Chunks) 的資料,讀一點、送一點、處理一點。
    • 結果:即使處理無限大的資料,也僅需佔用幾十 MB 的記憶體。

串流的核心優勢

  1. 記憶體效率 (Memory Efficiency):無需一次載入全部數據,實現空間換取時間的極大化。
  2. 啟動速度 (Time Efficiency):一旦接收到第一塊數據即可開始處理,不必等待整個傳輸完成,適合即時影音、動態壓縮等場景。

Node.js 的四種串流類型

  1. Readable (可讀取):資料的來源端(例如:fs.createReadStream, HTTP Request)。
  2. Writable (可寫入):資料的接收端(例如:fs.createWriteStream, HTTP Response)。
  3. Duplex (雙向):可讀也可寫的串流(例如:TCP Socket)。
  4. Transform (轉換):一種特殊的 Duplex,在資料流入與流出之間進行加工(例如:zlib 壓縮、加密)。

串流與事件 (EventEmitter)

所有串流都是 EventEmitter 的實例。它們透過發送特定事件來告知生產/消費進度:

  • data:有新數據塊進來了。
  • end:Readable 串流讀取完畢。
  • error:過程中發生異常。
  • finish:Writable 串流所有數據已寫入底層系統。

控制流的藝術:Piping 與 Backpressure

Piping (導管連結)

我們可以使用 .pipe() 方法,將 Readable 的輸出自動對接到 Writable 的輸入。

const fs = require('fs');
const src = fs.createReadStream('source.zip');
const dest = fs.createWriteStream('dest.zip');

// 自動連結 Readable 與 Writable
src.pipe(dest);

Backpressure (背壓)

什麼是背壓?當水龍頭(Readable)開得太大,而排水孔(Writable)處理太慢時,中間的緩衝區會爆滿。pipe() 方法內建了背壓管理:當 Writable 忙不過來時,它會通知 Readable 先停止發送,等到緩衝區清空後再繼續。

小提醒:雖然 pipe() 很方便,但它的錯誤處理較為麻煩。在現代 Node.js 中,更推薦使用 stream.pipeline(),它能自動管理生命週期並更優雅地處理各階段的 Errors。

總結

  1. 記得:Buffer 裝資料,Stream 運資料
  2. 只要處理的資料超過 100MB 或來自不穩定的網路,請務必優先考慮 Stream。
  3. Stream 讓 Node.js 的 I/O 性能真正問鼎後端開發榜首。