CodeGym /課程 /C# SELF /輸入輸出流: Stream

輸入輸出流: Stream

C# SELF
等級 36, 課堂 1
開放

1. 前言

想像一下你有一個裝水的熱水壺。你打開水龍頭,水就開始流出來。你可以一次裝滿一大壺水,也可以慢慢加一點一點。檔案也是一樣——不是每次都能或需要把整個檔案一次載入記憶體。檔案可能很大,有時資料來源甚至不是檔案,而是像網路連線這種資料會慢慢來的東西。

如果我們每次都只用 byte 陣列來處理,遇到大檔案很快就會爆記憶體,而且對於「無限」資料流(像影片或音訊串流)這種情境根本行不通。這時候,流(Stream)的概念就超級有用了!

在 .NET 裡,流就是一種讓你可以「順序存取資料」的抽象:不管背後是檔案、網路、記憶體,還是什麼奇怪的東西(像壓縮檔),你都可以分段(通常是區塊或 byte)來讀寫資料。

核心概念:

  • 流 就是資料傳輸的通道。很像輸送帶:你可以「放進去」(寫入)或「拿出來」(讀取)資料,不用管資料實際存在哪、怎麼存。
  • 資料是順序來的:你只能在讀完前一段後才能讀下一段(或反過來,如果支援倒帶)。
  • 大部分情況下,你不會一次把所有資料都塞進記憶體(電腦會感謝你的)。

這個抽象是 .NET 幾乎所有輸入輸出操作的基礎:檔案、網路、壓縮檔,甚至主控台!

2. 流 System.IO.Stream

繼承與架構:System.IO.Stream

幾乎所有 .NET 的流都繼承自抽象類別 System.IO.Stream。它定義了讀取、寫入、移動和管理流的基本方法。


classDiagram
    class Stream {
        +Read()
        +Write()
        +Seek()
        +CanRead
        +CanWrite
        +CanSeek
        +Length
        +Position
    }
    class FileStream
    class MemoryStream
    class NetworkStream
    class CryptoStream
    Stream <|-- FileStream
    Stream <|-- MemoryStream
    Stream <|-- NetworkStream
    Stream <|-- CryptoStream
.NET 流的繼承結構圖
  • Stream —— 基本抽象類別
  • FileStream —— 處理檔案用
  • MemoryStream —— 處理記憶體資料用
  • NetworkStream —— 處理網路資料用
  • CryptoStream —— 加解密用

快速認識流的重點屬性和方法

屬性 / 方法 說明
CanRead
這個流能不能讀
CanWrite
這個流能不能寫
CanSeek
能不能在流裡移動(不是每種流都支援)
Length
流的長度(如果支援的話,不是每種流都有)
Position
目前在流裡的位置
Read(...)
讀資料
Write(...)
寫資料
Seek(...)
在流裡移動
Flush()
清空緩衝區(把累積的資料一次寫進流)
Close()
/
Dispose()
關閉流並釋放資源

來看看實際上怎麼用吧。

3. 範例:用 Stream 讀寫檔案

這裡有個超簡單的範例,讓你看看流「實戰」怎麼玩:


// 開檔案準備寫入
using var stream = new FileStream("numbers.bin", FileMode.Create);

// 假設我們要把 1 到 10 的數字寫進檔案
for (int i = 1; i <= 10; i++)
{
    byte val = (byte)i;
    stream.WriteByte(val); // 一次寫一個 byte
}

// 明確關閉檔案,準備再打開來讀
stream.Close(); 

// 現在來把這些數字讀回來
using var stream2 = new FileStream("numbers.bin", FileMode.Open);
int value;
while ((value = stream2.ReadByte()) != -1)
{
    Console.WriteLine(value); // 會印出 1, 2, ... 10
}

這裡我們用 FileStream,它就是最標準的流:你可以分區塊或一個 byte 一個 byte 來讀寫資料。

流的類型:會在哪裡遇到?

流不一定只是磁碟上的檔案。這裡有幾個常見的流應用場景:

  • 磁碟檔案(像 FileStream —— 最常見)
  • 記憶體流(MemoryStream —— 很適合暫存或中間資料)
  • 網路連線(NetworkStream)
  • 壓縮/封存(GZipStream, DeflateStream)
  • 加密(CryptoStream)
  • 主控台輸入/輸出(沒錯!技術上也是流)

這讓你寫的程式不用管資料來源/目的地是什麼:只要是流就能通用!

4. 實用小細節

讀寫就是分段傳輸資料。通常會用 byte 陣列配合 Read、Write 方法。

範例:分區塊讀檔案

byte[] buffer = new byte[1024]; // 1024 byte(1KB)緩衝區
using var stream = new FileStream("bigfile.bin", FileMode.Open);

int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
{
    // 處理 buffer 裡的 bytesRead 個 byte
    int sum = 0;
    for (int i = 0; i < bytesRead; i++)
        sum += buffer[i];

    Console.WriteLine($"區塊總和: {sum}");
}

這種寫法到處都用得到——從防毒軟體到音樂播放器。

流的定位(Position, Seek)

大多數流(像檔案流)都可以在資料裡移動——你不一定只能讀「下一段」,也可以跳到指定位置開始處理。

using var stream = new FileStream("numbers.bin", FileMode.Open);
stream.Position = 5; // 跳到第 6 個 byte(從 0 開始算)
int value = stream.ReadByte();
Console.WriteLine($"檔案第 6 個 byte: {value}");

流有只讀、只寫,或兩者皆可

有些流只支援其中一種操作:

  • 只寫檔案:只能 Write()
  • 網路資料流:只能 Read()
  • 有些特殊情況(像印表機輸出流)根本不能倒帶或定位。

用 CanRead、CanWrite、CanSeek 屬性來檢查支援哪些操作:

using var stream = new FileStream("myfile.txt", FileMode.OpenOrCreate);
if (stream.CanRead)
    Console.WriteLine("支援讀取");
if (stream.CanWrite)
    Console.WriteLine("支援寫入");
if (stream.CanSeek)
    Console.WriteLine("可以在檔案裡移動");

流的緩衝

幾乎所有流都會用內部緩衝區來加速。緩衝可以減少磁碟/網路的存取次數:資料會先累積,然後一次處理。

Flush() 方法可以強制清空緩衝區(像是保證資料真的寫進磁碟):

using var stream = new FileStream("log.txt", FileMode.Append);
byte[] bytes = Encoding.UTF8.GetBytes("Hello, Stream!\n");
stream.Write(bytes, 0, bytes.Length);
stream.Flush(); // 保證資料真的寫進磁碟

如果你在寫很重要的資料(像金流交易!),記得呼叫 Flush()。

5. 新手常見錯誤

新手最常犯這些錯:

忘記關閉流(結果記憶體外洩、檔案鎖死,各種災難)。

搞混文字流和二進位流——用 byte 方法寫字串,結果讀出來全是亂碼。

緩衝區太小(或根本沒緩衝)——操作超慢。

以為 Read() 一定會讀到你要的 byte 數——其實可能更少;一定要檢查回傳值。

沒注意不是所有流都能定位(Seek),尤其是網路流。

像這樣:


// 壞範例:沒檢查實際讀到多少 byte 就全用
byte[] buffer = new byte[1024];
using (var stream = new FileStream("data.bin", FileMode.Open))
{
    int bytesRead = stream.Read(buffer, 0, 1024);
    // 如果檔案小於 1024,bytesRead 會更小!
}
2
任務
C# SELF, 等級 36, 課堂 1
上鎖
檢查 Stream 的屬性
檢查 Stream 的屬性
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION