不學 JAVA 換學 C# 之覺得心累 - L1:ch12 檔案與資料流
前言
在 C# 中,操作檔案與資料流是十分重要的,尤其當我們需要將資料儲存下來、讀取設定檔、紀錄使用日誌或處理大筆資料時。透過檔案與資料流的操作,我們可以有效地將程式與外部檔案進行互動。
暫存 vs 永久儲存
- 暫存 (Temporary / Volatile storage):存在記憶體 (RAM) 中,程式結束或斷電後資料會遺失,例如變數。
- 永久儲存 (Permanent / Nonvolatile storage):存在硬碟、USB 等儲存裝置中,不會因關機而消失,例如文字檔案。
檔案
檔案 (File) 是一組資訊的集合,儲存在永久儲存性裝置上。又分為:
- 文字檔案 (Text File):可使用文字編輯器開啟,如
.txt
,.csv
。 - 二進位檔案 (Binary File):儲存非文字資料,以二進為格式儲存,如圖片、音訊等。
Write 寫入
將資料從「記憶體 (RAM)」中複製到「儲存裝置的檔案」中。
Read 讀取
將資料從「儲存裝置的檔案」中複製到「記憶體 (RAM) 」中。
File、Directory 與 Path 靜態類別
在 System.IO
命名空間中,C# 提供一些靜態類別,讓我們不用建立物件,就能方便快速地處理檔案、資料夾和路徑,畢竟簡單地處理檔案、資料夾和路徑是不需要依賴個別物件的。
File 類別
操作檔案。
File.Exists(path)
:檢查檔案是否存在。File.GetCreationTime(path)
:取得檔案建立時間。File.ReadAllText(path)
:一次性讀取整個檔案內容為一個字串,適合用在小檔案、組態設定或純文字格式 (如.txt
,.json
,.csv
)。File.WriteAllText(path, content)
:一次性寫入整個字串到檔案中。如果檔案已經存在,會直接覆蓋原有內容。如果檔案不存在,會自動建立。File.OpenRead(path, content)
:回傳FileStream
檔案資料流,可以搭配StreamReader
來逐步讀取,適合大檔案。記得使用using
釋放資源。
Directory 類別
操作資料夾。
Directory.Exists(path)
:檢查資料夾是否存在。Directory.GetFiles(path)
:取得資料夾中所有檔案名稱陣列。
Path 類別
處理檔案或資料夾的路徑字串。
Path.Combine(path1, path2, path3)
:拼出完整路徑。Path.GetFileName(path)
:取得檔案名稱。
練習
1 | using System; |
搭配使用範例
1 | string folder = "C:\\Data"; |
位元 (bit) 和 位元組 (byte)
bit 是電腦中資料的最小單位,全名為 binary digit (二進位數位),它的值只能是 0
或 1
,分別代表關閉與開啟的狀態。在電腦系統中,所有資料最終都是由大量的 bit 所組成,例如文字、圖片、影片、聲音等資訊,都是以二進位的形式存在。
byte 是電腦儲存和處理資料時最基本的單位之一,由 8 個 bit (位元) 所組成。由於一個 bit 只能表示 0 或 1,資訊量非常有限,而一個 byte 則可以表示 256 種不同的數值 (從 0 到 255),足以儲存一個英文字元,例如 A
。因此,我們必須熟悉以 byte 為單位的資料大小,如:1KB = 1024 bytes。
檔案的資料結構
- 字元 (Character):資料最小單位,由 byte(s) 組成,而 1 byte = 8 bits。
- 欄位 (Field):有意義的字元集合,如姓名、員工編號。
- 紀錄 (Record):相關欄位集合,例如一位員工的完整資料。
- 檔案 (File):多筆員工資料的集合。
有兩種存取方式:
- Sequential Access (順序存取):資料依序讀寫,欄位也可以拿來當作每一筆紀錄的 id,適合日誌、報表。
- Random Access (隨機存取):可直接跳到檔案任意位置,適合資料庫應用。
資料流 Stream
資料流 (stream) 是在應用程式與裝置 (如硬碟、鍵盤、螢幕) 之間傳遞位元組 (byte) 的通道。它像水管一樣,把資料從輸入端 (像是鍵盤、檔案) 傳遞到程式,或從程式傳送到輸出端 (像是螢幕、檔案)。
鍵盤 -> 程式、程式 -> 螢幕:使用 Console
類別。
檔案 -> 程式、程式 -> 檔案:
FileStream
:直接操作檔案的原始位元組 (bytes),可以讀取.txt
、.jpg
、.mp3
、.exe
等任何檔案的內容 (不論格式),相比於讀取文字檔,更適合讀寫大型檔案或二進位資料 (例如圖片、音樂)。StreamReader
:讀取文字檔。StreamWriter
:寫入文字檔。
使用完 stream 需用 Close()
關閉,以確保資料寫入與釋放資源。
或是
使用 using
自動釋放資源,using
區塊結束時會自動呼叫 Dispose()
。
使用 FileStream
使用 FileStream
操作字串需要自己手動將字串轉成 byte,或將 byte 轉成字串。
寫入
建立一個新檔案,使用 UTF-8 編碼格式,把字串寫入檔案中。
- 建立一個檔案資料流物件
fs
,用來操作一個名稱為"example.txt"
的檔案,FileMode.Create
表示若檔案存在會覆蓋,不存在則建立新檔案,而FileAccess.Write
會將資料流設定為寫入模式。 - 將字串
"Hello World"
轉為 UTF-8 編碼的位元組陣列。 - 將位元組資料寫入檔案中,
0
是起始位置,data.Length
是要寫入的長度。 - 關閉檔案資料流,釋放資源並確保資料正確寫入。
1 | using System.Text; // 引入 `Encoding.UTF8` 所需的命名空間。 |
讀取
讀取檔案內容,並將位元組轉為字串顯示在 Console。
- 建立檔案資料流物件
fs
,開啟檔案"image.jpg"
,將資料流設定為唯讀模式。 - 檢查檔案大小是否超過陣列可用最大長度
int.MaxValue
。 - 檔案大小超過
int.MaxValue
:分批讀取
a. 建立固定大小的 buffer 位元組陣列 (緩衝區)。
b. 使用迴圈搭配fs.Read(...)
逐區塊讀取資料進入 buffer,並將結果轉成 UTF-8 字串,輸出到Console
。 - 檔案大小未超過
int.MaxValue
:一次性讀取
a. 建立和檔案一樣大的 buffer 位元組陣列。
b. 一次讀取整個檔案進入 buffer,將全部結果一次轉成 UTF-8 字串,並輸出到Console
。 - 關閉檔案資料流,釋放資源並確保資料正確寫入。
1 | FileStream fs = new FileStream("image.jpg", FileMode.Open, FileAccess.Read); |
使用 StreamWriter
、StreamReader
如果確定處理的是文字資料 (例:文字檔、CSV、設定檔),直接使用 StreamWriter
和 StreamReader
,因為它們會自動處理文字與位元組的編碼和解碼,不需要手動將字串轉成 byte。
寫入
1 | StreamWriter sw = new StreamWriter("data.txt"); |
改用 using
的寫法 (自動關閉資源):
1 | using (StreamWriter sw = new StreamWriter("data.txt")) |
讀取
1 | StreamReader sr = new StreamReader("data.txt"); |
改用 using
的寫法 (自動關閉資源):
1 | using (StreamReader sr = new StreamReader("data.txt")) |
使用 FileStream
搭配 StreamWriter
或 StreamReader
有點多此一舉,但是也可以這樣搭配使用。
寫入
1 | using (FileStream fs = new FileStream("data.txt", FileMode.Create, FileAccess.Write)) |
讀取
1 | // 條狀 using (C# 8.0+ 的寫法) |
支援隨機存取
可用 Seek(offset, origin)
方法重設讀取指標位置,offset
是以位元組為單位的偏移量 ,而 origin
是參考起點。
注意:Seek()
只適用於支援可定位的 stream,否則會拋出例外。
1 | fs.Seek(0, SeekOrigin.Begin); // 從頭開始重讀 |
序列化和反序列化 (Serialization / Deserialization)
序列化
將物件轉換為可以儲存的格式,例:JSON、XML 等。主要是為了方便將物件儲存在資料庫、記憶體或檔案中。
反序列化
將儲存的資料還原回原本的物件。
使用原因
- 可將物件直接轉為儲存格式儲存起來,再從儲存格式還原為物件,序列化時會自動處理欄位順序、類型、巢狀結構,資料結構不會錯亂。
- 比文字儲存更安全,因為可附加加密或簽章。
二進位格式 BinaryFormatter
BinaryFormatter
在 .NET 5.0 之後被標記為不安全並建議避免使用,適合用來學習什麼是序列化和反序列化,但實務上推薦用 System.Text.Json
或 XmlSerializer
等方式。
使用 BinaryFormatter
一定要在 class 上加 [Serializable]
屬性,否則會拋出例外。
1 | using System; |
JSON 格式 JsonSerializer
1 | using System; |