—— 對這個問題的思考來源於前幾天對 Java Socket 編程的嘗試,TCP 協議要求創建一個 Socket 鏈接(著名的三次握手)以後才能進行通訊,而鏈接雙方進行數據的發送與接受,都是經過對輸入輸出流的機制來完成的。java
流做爲概念應該是語言無關的。文件IO流,Unix系統標準輸入輸出流,標準錯誤流(stdin, stdout, stderr),還有一開始提到的 TCP 流,還有一些 Web 後臺技術(如Nodejs)對HTTP請求/響應流的抽象,均可以見到流的概念。編程
K&R 在 C Programming Language 書中提到流是這樣定義的:數組
流 (stream) 是與磁盤或其它外圍設備關聯的數據的源或目的地。ruby
能夠把流理解成是對程序與外界交換數據的一種抽象,這裏的外界限定是有必要的,一般不會把程序內部的數據流動抽象爲流,畢竟在程序內部,數據流動是由函數調用、返回來完成的。而當咱們使用三個標準IO流時,咱們關心的是怎樣經過它們與外界交互;當咱們使用文件流時,咱們關心的是將內存中的數據持久化到磁盤文件中(或從磁盤中讀數據導內存)。數據結構
因而數據從 A 處「流」向 B 處,能夠類比像水流同樣從高處流向低處。在水流動的過程當中,做爲最基本物理組成單位的水分子是不變的,相應的數據流也有它最小的組成單位。在不一樣的編程語言中,這個最小單位一般是字節流(二進制流)中的字節,或者字符流(文本流)中的字符。編程語言
——但不會是其餘數據類型,就像咱們歷來沒據說過數字流?,或者浮點數流,甚至數組流? 函數
由於字節是計算機保存數據的最終形式,而字符是其它數據結構序列化後的表現形式,也是人能夠閱讀的形式。與外界的交互須要這些通用的格式。不關心數據的內容,只須要完整地傳輸原始數據時,考慮字節流便可;關心傳輸字符和字符串時,就須要對字符流進行操做,stdio.h
頭文件裏那一大坨輸入輸出函數就是幹這個的。好比fgetc(FILE *stream)
從文本流中讀入一個字符。學習
另外一方面,根據數據流動的方向,能夠再抽象出輸出流和輸入流的概念。從程序內部到外部的流向是輸出流,從程序外部到內部的流向是輸入流。ui
C 語言的stdio.h
庫中定義了打開文件流時必須指定的集中打開方式,"r"
表示用於讀取,"w"
用於寫入,"r+"
用於讀寫。相似地,Java 語言的java.io
包中包含了InputStream
, OutputStream
明確區分的輸入流類和輸出流類,而且兩者都是抽象類,意味着必須根據須要使用它們各自的子類進行實例化。命令行
根據實際的代碼能夠幫助理解stream,下面是一段用C語言標準庫實現的最簡單的文件拷貝功能。
出於學習目的,這段代碼偷懶沒有任何容錯功能,是典型的反面教材, 不過 whatever 了,不信你真拿去編譯一下,是真的能夠完整拷貝文件!除了不能拷貝目錄,不能拷貝不存在的文件,不能拷貝文件權限,不能漏掉目的文件名或者路徑,不能靈活處理文件軟連接硬連接。等等等等blahblah(因此其實連看上去很簡單的cp
程序也是要有一大坨因素要考慮和支持的(啊跑題了
// mini_cp.c #include <stdio.h> #define BUFFER_SIZE 512 int main(int argc, char *argv[]) { // 從命令行參數中得到 SOURCE 和 DES 文件流 FILE *src = fopen(argv[1], "rb"); FILE *des = fopen(argv[2], "wb"); long int num; // buffer 是讀寫的緩衝數組 char buffer[BUFFER_SIZE]; while(!feof(src)) { num = fread(buffer, sizeof(char), BUFFER_SIZE, src); fwrite(buffer, sizeof(char), num, des); } fclose(src); fclose(des); return 0; }
這個自制的mini_cp
程序不難理解,核心的邏輯能夠分解爲三個步驟:
打開源文件流FILE *src
和目的文件流FILE *des
循環執行 { 每次從src
流讀取最多512字節的數據 => 並寫入des
流 } 直到源文件讀取結束
關閉文件流
核心邏輯是很是清晰明瞭的,這樣的邏輯也是流操做的廣泛原理,嘗試其餘語言的實現,其實都已經大同小異,每每都少不了一個緩衝區
的概念(或對象)。
來看一下 Java 版本的同等實現:
import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class Copy { private static final int BUFFER_SIZE = 512; public static void main(String[] args) throws IOException { File srcFile = new File(args[0]); File desFile = new File(args[1]); int recvBytesSize; byte[] buffer = new byte[BUFFER_SIZE]; FileInputStream in = new FileInputStream(srcFile); FileOutputStream out = new FileOutputStream(desFile); while((recvBytesSize = in.read(buffer)) != -1) { out.write(buffer, 0, recvBytesSize); } in.close(); out.close(); } }
面向對象味更濃(代碼更冗長)了有木有?但也正是由於面向對象,Java 把理論上的 stream 抽象爲類,讓咱們直接得到類的實例(即對象),從而對對象進行操做。仍是挺不賴的是吧,雖然代碼更長了沒錯,可是更 OO 啊~
寫到這裏已經能回答流基本是怎麼一回事了,那麼最後順便再來放一段拷貝程序的ruby實現;
require 'fileutils' FileUtils.cp('SOURCE.txt', 'DEST.txt')
哈?
嗯。
... That's why we love Ruby...(逃。。。