流(stream)是怎麼一回事

—— 對這個問題的思考來源於前幾天對 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...(逃。。。

相關文章
相關標籤/搜索