一些「流與管道」的小事

「流」這個概念在開發中很是常見,在 java 語言裏咱們熟知InputStreamOutputStream,node 中有WriteStreamReadStream,cpp 裏也有 stream… 彷佛這是編程語言裏不可或缺的一部分。而初學者通常會照着文檔完成咱們的程序卻對流自己並非特別瞭解。若是你是科班出身的話,老師會和你說過,「流」顧名思義,就像水流同樣,從這一端流向那一端。那麼流能爲咱們提供什麼呢?爲何它會存在呢?javascript

舉個簡單的例子,若是咱們須要順序讀取或者寫入文件裏的內容的話(非隨機讀寫),咱們必定會用到流,咱們能夠打開一個輸出流,流以文件數據爲源,開發者代碼爲輸出端造成一個管道。咱們在代碼裏那一端操做read就能夠獲取到文件內容。這是一個典型的從一個水桶中用管子把水抽到另一個水桶中的過程。這個過程彷佛沒什麼神奇的,咱們依靠他完成了簡單的讀寫操做。java

Stream

若是這時候,咱們擁有一個能夠基於一個或者個字節就能壓縮和解壓的算法呢?咱們能夠先從文件流中讀幾個字節,而後通過算法處理後,把處理結果輸出去,這樣對於外部操做者來講,其實並沒有感知—開發者依舊是「拿着水桶接水」而已,無論這個「水」是「純淨水」仍是別的玩意兒。那麼,這種操做對於咱們來講,帶來了一種可能,這種可能就是流的變換 (Transform) 。比如在源頭和咱們接收端的當中加入了一些中間件,把咱們的管子拆開,連了中間件的管子,不斷的爲流過的每一個字節作轉換。沒錯,咱們剛提到的那個壓縮算法想必你們都很是熟悉,就是gzip算法。node

GZIP

流的變換

基於這種 Transform 的應用,咱們在 node 中會常常看見一個叫pipe的方法,對於流的操做,咱們能夠用 pipe 方法串聯起來,就像這樣linux

fs.createReadStream(...)
.pipe(gzip())
.pipe(other())
.pipe(fs.createWriteStream(...))

這種基於流處理的聲明式編程範式使得咱們的業務流程很是的清晰,若是咱們把中間過程比喻成爲「水「染色的話,咱們的 pipe 更像是一個提供「染色功能的管道」,爲何要強調這一點,咱們能夠想象到,對水進行染色的話,咱們是不須要把全部的水先放在一個桶裏,而後再在桶裏加染色劑,再把染色好的水倒到另外的一條管子裏的 也就是說,咱們面向流的操做,不少狀況下會比通常的方案更加省內存! 很顯然咱們有許多種辦法辦到,咱們可使用少許的內存(緩存),好比當咱們須要把4個字節轉成 1 個 Int 時,咱們可能就須要一個功能(想一想 Java 中用到的 BufferedInputStream/BufferedOutputStream 類)流的最小單元是字節,可是咱們能夠利用緩存,把流式處理改爲塊式(幀式)處理的方式,處理完後能夠變成另一種流。算法

管道

流的容器,其實就是管道。管道能夠擁有 Buffer 也能夠不擁有 Buffer,若是你須要處理一塊的數據的時候,通常都會須要緩衝區。注意這裏塊的概念,通常都是有邊界或者固定長度的一組字節。咱們這裏再作一個想象,把WriteStreamReadStream看作一條管道的兩端,管道里面可能有緩衝區,管道最基礎的數據類型是字節,一個管道的兩端能夠任意對接其餘的管道,每一個管道會對通過的數據作必定的處理,或是修改字節,或是類型變換。編程

Transformer

那麼這麼一整套的模型,就是咱們的管道編程模型。管道不算是一個特別抽象的概念,他在操做系統中很是常見,最經典的就是咱們的標準輸入和標準輸出。標準輸入是一個寫入端,管道的另外一頭鏈接着這個進程。咱們在寫入端寫入一些數據,進程便可讀到。這就可讓咱們達到進程間通訊(IPC)的目的。若是一個進程接收標準輸入和執行標準輸出,那麼咱們進行進程間通訊的成本將很是的低廉——只要把目標進程的 Std IO 重定向到咱們這邊生成的管道便可。緩存

OS 中的 Stream 和 I/O

咱們已經瞭解到了管道是一個總體的概念,那麼咱們常常遇見的 OS 級別的流編程模型有哪些呢?其實很是常見,包括咱們前文介紹的,那麼主要有如下幾種:網絡

  1. 標準輸入輸出 (stdio)
  2. 文件
  3. 管道(匿名管道和命名管道)
  4. Socket (網絡套接字)
  5. tty (終端)
  6. unix local socket

那麼對於這些文件的讀寫,咱們能聽到的最多見的名詞就是 I/O 了,在 unix 的世界裏,全部的流都以文件描述符的方式進行描述(File Descriptor 縮寫成 fd),咱們在 C 語言的編程過程當中,可能對於這個名詞很是的熟悉了,咱們彷佛還聽過一句話:「linux 把一切可讀寫的玩意兒都比做文件」,可能就是這個意思吧。socket

咱們可使用 linux 提供的open函數來獲取fd,而後使用readwrite進行對fd的操做,一旦你把readwrite進行了屢次封裝,那麼它就變成了stream transformer像咱們以前介紹的那樣,linux 提供的對I/O的系統調用,是咱們概念的基石。編程語言

I/O 模型

實際上,若是要寫出優雅範式的代碼,咱們可能對於I/O 模型還須要作一些瞭解,在三大操做系統中,各有本身最優的I/O 模型實現

OS I/O 模型
Windows IOCP (完成端口)
Linux epoll
macOS(Darwin) kqueue

他們之間有很是多的類似點,若是你爲特定的操做系統提供 I/O 操做,那麼你應該選擇該系統下面最優的模型去作,這樣才能讓你的 I/O 效率(流讀寫效率)最大化,本文暫時再也不贅述,有想了解的同窗能夠自行查閱相關資料。

原文連接: https://geminiwen.com/archive...

相關文章
相關標籤/搜索