前兩天我們學了非同步程式設計(async / await),今天要來看看實務上非常常見的一個應用場景──檔案存取 (File I/O)。不論是紀錄日誌、讀取設定檔,還是匯入匯出資料,都會需要操作檔案。C# 提供了許多方便的類別,像 File, FileInfo, StreamReader, StreamWriter 等等,可以快速完成這些任務。而且這些操作也能配合非同步寫法 (File.ReadAllTextAsync, File.WriteAllTextAsync),讓程式在讀寫大型檔案時不會卡住 UI 或主執行緒。
檔案與資料流輸入/輸出(I/O, Input/Output)指的是資料在儲存媒介之間的傳輸。在 .NET 中,System.IO 命名空間包含了可用來同步與非同步地讀取與寫入資料流與檔案的型別。這些命名空間也提供了用於壓縮與解壓縮檔案的型別,以及可透過 管道(pipes)與序列埠(serial ports) 進行通訊的型別。
檔案是一個有順序且具名稱的位元組集合,並具有持久性的儲存空間,當你處理檔案時,會涉及目錄路徑、磁碟儲存以及檔案與目錄名稱。相較之下, 資料流(stream) 是一連串的位元組,可用來從後端儲存體讀取或寫入資料,而這個後端儲存體可能是磁碟、記憶體或其他媒介。不僅僅有檔案資料流,也有網路資料流、記憶體資料流與管道資料流等其他種類。可以使用 System.IO 命名空間中的類別與檔案或目錄互動,例如:
常用的檔案與目錄類別如下:
類別 | 說明 |
---|---|
File |
提供建立、複製、刪除、移動與開啟檔案的靜態方法,並可建立 FileStream 物件。 |
FileInfo |
提供上述動作的執行個體方法。 |
Directory |
提供建立、移動及列舉目錄與子目錄的靜態方法。 |
DirectoryInfo |
提供上述動作的執行個體方法。 |
Path |
提供跨平台處理目錄字串的方法與屬性。 |
抽象基底類別 Stream 支援讀取與寫入位元組,所有表示資料流的類別都繼承自 Stream。Stream 類別及其衍生類別提供對資料來源與儲存體的共同介面,讓開發者不需關心作業系統或底層裝置的細節。
資料流的三個基本操作為:
不同資料來源可能支援不同的功能,例如 PipeStream 不支援定位操作。可使用屬性 CanRead、CanWrite、CanSeek 來檢查資料流是否支援特定操作。
常用的資料流類別如下:
類別 | 功能 |
---|---|
FileStream |
用於讀寫檔案。 |
IsolatedStorageFileStream |
用於隔離儲存區中的檔案。 |
MemoryStream |
用於記憶體中讀寫資料。 |
BufferedStream |
改善讀寫效能。 |
NetworkStream |
用於網路通訊的資料流。 |
PipeStream |
用於匿名或具名管道的資料流。 |
CryptoStream |
將資料流連結至加密/解密轉換。 |
System.IO 命名空間也提供了處理文字編碼字元的讀寫類別。資料流通常以位元組為單位進行 I/O,而讀取器與寫入器會在位元組與字元之間進行轉換。每個讀寫類別都關聯至一個資料流,可透過 BaseStream 屬性取得。
常用的讀寫類別如下:
類別 | 功能 |
---|---|
BinaryReader / BinaryWriter |
以二進位格式讀寫基本資料型別。 |
StreamReader / StreamWriter |
使用指定編碼讀寫文字。 |
StringReader / StringWriter |
從字串中讀取或寫入字元。 |
TextReader / TextWriter |
為所有文字讀寫器的抽象基底類別。 |
在處理大量資料時,應使用非同步操作以避免 UI 卡頓,同步 I/O 會阻塞 UI 執行緒直到操作完成,而非同步 I/O 可讓應用程式保持回應。非同步成員的名稱中通常包含 Async,例如:
這些方法可搭配 async 與 await 關鍵字使用。
壓縮是將檔案大小縮小以便儲存;解壓縮則是還原成可使用的格式。System.IO.Compression 命名空間包含壓縮與解壓縮檔案與資料流的型別。
常用類別如下:
類別 | 功能 |
---|---|
ZipArchive |
建立與存取 zip 壓縮檔案中的項目。 |
ZipArchiveEntry |
代表壓縮檔中的單一檔案。 |
ZipFile |
建立、解壓與開啟壓縮檔。 |
ZipFileExtensions |
針對壓縮封裝建立或提取項目。 |
DeflateStream |
使用 Deflate 演算法進行壓縮/解壓。 |
GZipStream |
使用 gzip 格式進行壓縮/解壓。 |
隔離儲存是一種具安全性與隔離性的儲存機制,透過使用者、組件(assembly)與網域來分隔資料。這對於應用程式無法直接存取使用者檔案的情況特別有用,例如儲存設定檔或暫存資料。隔離儲存在 Windows 8.x 應用程式中不可用,請改用 Windows.Storage 命名空間中的應用程式資料類別。常用類別如下:
using System;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
string filePath = "example.txt";
string content = "Hello, IT 鐵人賽!這是我的第 20 天學習紀錄。";
// 非同步寫入
await File.WriteAllTextAsync(filePath, content);
Console.WriteLine("✅ 寫入完成!");
// 非同步讀取
string result = await File.ReadAllTextAsync(filePath);
Console.WriteLine("📄 檔案內容:");
Console.WriteLine(result);
}
}
執行結果:
✅ 寫入完成!
📄 檔案內容:
Hello, IT 鐵人賽!這是我的第 20 天學習紀錄。
可以看到在目錄多了一個example.txt的檔案,並且把Hello, IT 鐵人賽!這是我的第 20 天學習紀錄。寫進檔案內。
2. 進階範例:逐行寫入與讀取 (StreamWriter / StreamReader)
有時候我們不想一次載入整個檔案,而是想一行一行處理:
using System;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
string path = "log.txt";
// 寫入多行文字
using (StreamWriter writer = new StreamWriter(path))
{
for (int i = 1; i <= 3; i++)
{
await writer.WriteLineAsync($"第 {i} 行:紀錄時間 {DateTime.Now}");
}
}
// 讀取多行文字
using (StreamReader reader = new StreamReader(path))
{
string? line;
while ((line = await reader.ReadLineAsync()) != null)
{
Console.WriteLine(line);
}
}
}
}
執行結果:
第 1 行:紀錄時間 10/5/2025 9:46:36 PM
第 2 行:紀錄時間 10/5/2025 9:46:36 PM
第 3 行:紀錄時間 10/5/2025 9:46:36 PM
而一樣可以在目錄看到log.txt檔。