[NodeJS] Streams流式數據處理

来源:https://www.cnblogs.com/feixianxing/p/18290492/node-js-streams
-Advertisement-
Play Games

本文介紹了NodeJS中流(Stream)的概念、類型和應用。流通過將數據分成小塊進行處理,優化了記憶體使用和數據處理效率。文章涵蓋了四種基本流類型:可讀流、可寫流、雙工流和轉換流,並通過實例代碼演示瞭如何使用流進行高效的數據傳輸和處理。 ...


在現代應用開發中,數據處理的效率和資源管理尤為重要。NodeJS作為一種高效的JavaScript運行時環境,通過其強大的流(Stream)功能,提供了處理大規模數據的便捷方式。流式數據處理不僅能夠優化記憶體使用,還可以提高數據處理的實時性和效率。下文將介紹NodeJS中的流概念、流的類型以及如何利用流來進行數據傳輸和處理。

流的基本概念

流式數據的特點是將數據分成一個一個的chunk,每次操作只針對其中的一小部分。

因此流式數據的讀寫操作不需要將整個數據保存在記憶體中(處理完就丟掉)。

常用於視頻這種包含大量數據的應用場景,也可以在時間和空間角度上更有效地處理數據:

  • 時間:從開始讀到流就可以處理數據並反饋給用戶了,不需要等待全部數據到達,例如:ChatGPT的回答,就是流式數據傳輸,一個字一個字地顯示出來;
  • 空間:如上文所說,在某些場景下不需要將整個數據保存在記憶體中。

NodeJS提供的API

NodeJS中的node:stream模塊提供了對流數據進行處理的抽象介面。

NodeJS中的所有流對象都可以監聽和觸發事件,都是EventEmitter的實例對象。

下麵的表格列出了每一種基本流常用且重要的事件

NodeJS中有四種基本的流類型:可讀流、可寫流、雙工流和轉換流。

描述 案例 事件 方法
可讀流 Readable Streams 可用於讀(消費)數據 1. http request
2. fs read streams
data
end
pipe()
read
可寫流 Writable Streams 可用於寫(生產)數據 1. http responses
2. fs write streams
drain
finish
write()
end()
雙工流 Duplex Streams 可讀可寫 net 網路套接字
轉換流 Transform Streams 雙工流,在讀寫的時候可修改 zlib Gzip creation

流式數據傳輸案例

簡介:創建一個比較大的文本文件,使用NodeJS啟動一個服務,介面分別以三種方法返迴文件內容。

代碼

方法一 不使用流

讀取整個文件的內容之後再返回;

讀取大文件的時候不推薦這樣寫,因為整個文件會先被完整地從磁碟讀取到記憶體中,再返回給客戶端。

import fs from 'node:fs';
import http from 'node:http';

const server = http.createServer();

server.on('request', (req, res)=>{
  // CORS
  res.setHeader('Access-Control-Allow-Origin', '*');

  // Solution 1
  fs.readFile('test.txt', (err, data)=>{
    if(err)console.log(err);
    res.end(data);
  });
});

server.listen(3000, ()=>{
  console.log('listening...');
});
方法二 可讀流

使用可讀流,優點是邊讀文件邊返回,只有當前處理的chunk會占據記憶體;

import fs from 'node:fs';
import http from 'node:http';

const server = http.createServer();

server.on('request', (req, res)=>{
  // CORS
  res.setHeader('Access-Control-Allow-Origin', '*');

  // Solution 2: Streams
  const readable = fs.createReadStream('test.txt');
  readable.on('data', (chunk)=>{
    res.write(chunk);
  });
  readable.on('end', ()=>{
    res.end();
  });
  readable.on('error', (err)=>{
    console.log(err);
    res.statusCode = 500;
    res.end('File reading error!');
  });
});

server.listen(3000, ()=>{
  console.log('listening...');
});
backpressure

這裡介紹一下流控(Flow Controll)領域中的一個名詞:Backpressure(翻譯為 反壓/背壓)。

在Node.js和其他流處理系統中,backpressure(反壓/背壓)是指生產者生成數據的速度超過消費者處理數據的速度時產生的一種控制機制。

當可讀流(Readable Stream)讀取數據的速度快於可寫流(Writable Stream)寫入數據的速度時,就會產生backpressure。為了防止這種情況,可讀流會根據可寫流的消費能力進行控制,暫停或減慢讀取數據的速度。

具體機制

  1. 可寫流的緩衝區:可寫流內部有一個緩衝區,用於暫存數據。如果這個緩衝區被填滿,流會返回 false,表示消費者已經無法及時處理更多的數據。
  2. 暫停和恢復:當可寫流返回 false 時,可讀流會暫停讀取數據。只有在可寫流的緩衝區有足夠的空間後,可讀流才會恢復讀取。
  3. 事件驅動:Node.js 流通過事件驅動的方式處理backpressure。當可寫流的緩衝區有空間時,會觸發 drain 事件,通知可讀流繼續讀取數據。

示例代碼:通過手動暫停和恢複合理利用緩衝區,避免數據丟失、記憶體溢出和資源耗盡。

import fs from 'node:fs';

const readableStream = fs.createReadStream('input.txt');
const writableStream = fs.createWriteStream('output.txt');

readableStream.on('data', (chunk)=>{
  const canWrite = writableStream.write(chunk);
  // 可寫流的緩衝區空間不夠了,暫停讀數據(生產)
  if(!canWrite){
    readableStream.pause();
  }
});

// 當可寫流的緩衝區空間足夠,會觸發`drain`事件
// 可以繼續讀數據
writableStream.on('drain', ()=>{
  readableStream.resume();
});

// 讀取結束,停止寫入
readableStream.on('end', ()=>{
  writableStream.end();
  console.log('done.');
});
pipe

在 Node.js 中,pipe 方法提供了一種更簡單和自動化的方式來處理流之間的 backpressure 問題。pipe 方法可以連接可讀流和可寫流,並自動處理 backpressure,無需手動暫停和恢復流。

示例代碼

import fs from 'node:fs';

const readableStream = fs.createReadStream('input.txt');
const writableStream = fs.createWriteStream('output.txt');

// 統一錯誤處理函數
function handleError(err) {
  console.error('發生錯誤:', err);
}

// 使用 pipe 連接可讀流和可寫流,並處理錯誤
readableStream.pipe(writableStream)
  .on('error', handleError);

// 處理可讀流和可寫流的錯誤
readableStream.on('error', handleError);
writableStream.on('error', handleError);

語法是:

readableSource.pipe(writableDestination);

接下來回到上文的關於流式數據網路傳輸的案例。

方法三 pipe

使用pipe可以簡化許多代碼,核心代碼就是

readable.pipe(res);

示例代碼:

import fs from 'node:fs';
import http from 'node:http';

const server = http.createServer();

server.on('request', (req, res)=>{
  // CORS
  res.setHeader('Access-Control-Allow-Origin', '*');

  // Solution 3: Pipe
  const readable = fs.createReadStream('test.txt');
  readable.pipe(res).on('error', ()=>{
    res.statusCode = 500;
    res.end('File reading error!');
  });
});

server.listen(3000, ()=>{
  console.log('listening...');
});

總結

  • 流(Stream)在NodeJS中的工作原理是將數據分成一個個小塊進行處理,這樣無需將整個數據載入到記憶體中,從而優化了記憶體使用和數據處理效率。
  • 流在NodeJS中有四種基本類型:可讀流、可寫流、雙工流和轉換流,每種類型都有其特定的應用場景和事件機制。
  • 流的應用場景主要包括視頻播放、文件處理、實時數據傳輸等。在這些場景中,流通過邊讀邊寫、邊處理邊傳輸的方式,可以有效地提高數據處理的實時性和系統的性能。

參考

[1] B站 - NodeJS教程
[2] 知乎 - 如何形象的描述反應式編程中的背壓(Backpressure)機制?


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • UIButton用於創建可交互的按鈕。按鈕可以響應用戶的觸摸事件,執行特定的動作或邏輯。 創建和配置UIButton 創建UIButton的基本步驟: // 創建UIButton實例,指定按鈕類型為系統類型 UIButton *button = [UIButton buttonWithType:UI ...
  • 在Objective-C進行iOS開發中,UILabel是一個非常基礎且常用的UI組件,用於在應用界面上顯示一段靜態文本。UILabel屬於UIKit框架的一部分,提供了豐富的屬性來控制文本的顯示方式,包括文本內容、字體、顏色、對齊方式、行數等。 創建和配置UILabel 創建一個UILabel實例 ...
  • 在iOS開發中,UITableView和UICollectionView是兩個非常核心的用於展示集合數據的UI組件。它們都能以列表的形式展示數據,但各自的特點和使用場景有所不同。 UITableView UITableView用於展示和管理垂直滾動的單列數據列表。它是以行的形式展示數據,每行(cel ...
  • 咱們國內現在手機分為兩類,Android手機與蘋果手機,現在用的各類APP,為了手機的使用安全,避免下載到病毒軟體,官方都極力推薦使用手機自帶的應用商城進行下載,但是國內Android手機品類眾多,手機商城各式各樣,做不到統一,所以Android的APP上架得一個一個平臺去申請上架,一直讓開發人員頭... ...
  • UINavigationController 是 iOS 中用於管理視圖控制器層次結構的一個重要組件,通常用於實現基於堆棧的導航。它提供了一種用戶界面,允許用戶在視圖控制器之間進行層次化的導航,例如從列表視圖到詳細視圖。 UINavigationController 的主要功能 管理視圖控制器堆棧: ...
  • UITabBarController 是 iOS 中用於管理和顯示選項卡界面的一個視圖控制器。它允許用戶在多個視圖控制器之間進行切換,每個視圖控制器對應一個選項卡。 主要功能 管理多個視圖控制器: UITabBarController 管理一個視圖控制器數組,每個視圖控制器對應一個選項卡。 顯示選項 ...
  • 在MVC模型中,V指view,負責用戶界面的顯示、處理用戶輸入,並將輸入傳遞給控制器。C是指ViewController,充當模型和視圖之間的中介。控制器接收用戶輸入,處理用戶請求,並將結果傳遞給視圖以更新顯示。本文詳細介紹在iOS開發中UIView與UIViewController的生命周期。 U ...
  • ‍ 寫在開頭 點贊 + 收藏 學會 理解 forEach JavaScript 的forEach方法是一種流行的數組迭代工具。它為每個數組元素執行一次提供的函數。但是,與傳統的for 和 while迴圈不同,forEach它被設計為對每個元素執行該函數,沒有內置機制來提前停止或中 ...
一周排行
    -Advertisement-
    Play Games
  • 通過WPF的按鈕、文本輸入框實現了一個簡單的SpinBox數字輸入用戶組件並可以通過數據綁定數值和步長。本文中介紹了通過Xaml代碼實現自定義組件的佈局,依賴屬性的定義和使用等知識點。 ...
  • 以前,我看到一個朋友在對一個系統做初始化的時候,通過一組魔幻般的按鍵,調出來一個隱藏的系統設置界面,這個界面在常規的菜單或者工具欄是看不到的,因為它是一個後臺設置的關鍵界面,不公開,同時避免常規用戶的誤操作,它是作為一個超級管理員的入口功能,這個是很不錯的思路。其實Winform做這樣的處理也是很容... ...
  • 一:背景 1. 講故事 前些天有位朋友找到我,說他的程式每次關閉時就會自動崩潰,一直找不到原因讓我幫忙看一下怎麼回事,這位朋友應該是第二次找我了,分析了下 dump 還是挺經典的,拿出來給大家分享一下吧。 二:WinDbg 分析 1. 為什麼會崩潰 找崩潰原因比較簡單,用 !analyze -v 命 ...
  • 在一些報表模塊中,需要我們根據用戶操作的名稱,來動態根據人員姓名,更新報表的簽名圖片,也就是電子手寫簽名效果,本篇隨筆介紹一下使用FastReport報表動態更新人員簽名圖片。 ...
  • 最新內容優先發佈於個人博客:小虎技術分享站,隨後逐步搬運到博客園。 創作不易,如果覺得有用請在Github上為博主點亮一顆小星星吧! 博主開始學習編程於11年前,年少時還只會使用cin 和cout ,給單片機點點燈。那時候,類似async/await 和future/promise 模型的認知還不是 ...
  • 之前在阿裡雲ECS 99元/年的活動實例上搭建了一個測試用的MINIO服務,以前都是直接當基礎設施來使用的,這次準備自己學一下S3相容API相關的對象存儲開發,因此有了這個小工具。目前僅包含上傳功能,後續計劃開發一個類似圖床的對象存儲應用。 ...
  • 目錄簡介快速入門安裝 NuGet 包實體類User資料庫類DbFactory增刪改查InsertSelectUpdateDelete總結 簡介 NPoco 是 PetaPoco 的一個分支,具有一些額外的功能,截至現在 github 星數 839。NPoco 中文資料沒多少,我是被博客園群友推薦的, ...
  • 前言 前面使用 Admin.Core 的代碼生成器生成了通用代碼生成器的基礎模塊 分組,模板,項目,項目模型,項目欄位的基礎功能,本篇繼續完善,實現最核心的模板生成功能,並提供生成預覽及代碼文件壓縮下載 準備 首先清楚幾個模塊的關係,如何使用,簡單畫一個流程圖 前面完成了基礎的模板組,模板管理,項目 ...
  • 假設需要實現一個圖標和文本結合的按鈕 ,普通做法是 直接重寫該按鈕的模板; 如果想作為通用的呢? 兩種做法: 附加屬性 自定義控制項 推薦使用附加屬性的形式 第一種:附加屬性 創建Button的附加屬性 ButtonExtensions 1 public static class ButtonExte ...
  • 在C#中,委托是一種引用類型的數據類型,允許我們封裝方法的引用。通過使用委托,我們可以將方法作為參數傳遞給其他方法,或者將多個方法組合在一起,從而實現更靈活的編程模式。委托類似於函數指針,但提供了類型安全和垃圾回收等現代語言特性。 基本概念 定義委托 定義委托需要指定它所代表的方法的原型,包括返回類 ...