The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

如何在 .NET Core / .NET 5+ / ASP.NET Core 正確使用 Big5 編碼

由於從 .NET Core 1.0 開始,就沒有自動載入 BIG5 編碼的 Encoding 資料,所以你沒辦法直接透過 Encoding.GetEncoding("Big5") 取得 Encoding 物件。在 .NET Core 3.1 之前,還需要須額外安裝 System.Text.Encoding.CodePages 套件才行。不過,從 .NET 5 開始,這個套件成為了 .NET SDK 的一部分,不再需要額外安裝。但即便如此,你還是要特別執行一段註冊以使用此編碼。接下來,我們將深入探討如何註冊這些編碼並正確使用它們在不同的場景中。

An abstract digital artwork representing text encoding and character conversion in .NET Core.

快速摘要

如果需要取得 Encoding.GetEncoding("Big5") 編碼,請在程式啟動時加入以下程式碼:

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

Encoding 概述

System.Text.Encoding 是 .NET 中用於文字編碼轉換的核心類別。它的作用是在字元位元組之間進行轉換:所謂編碼 (Encoding) 就是將一組 Unicode 字元轉換成對應的位元組序列,反之則稱為解碼 (Decoding)。

由於 .NET 字串 (string) 內部使用 UTF-16 編碼 (Unicode 編碼) 來表示文字,而每個 char 通常對應 Unicode 碼元(Code Unit),Encoding 類別的角色就是在程式的 Unicode 字串與各種外部編碼形式之間架起橋樑。例如,讀取檔案或網路資料時,可能需要將位元組依照正確的編碼解碼成 .NET 字串;反之,寫出文字或傳輸資料時,也需要依照指定編碼將字串轉為位元組

需要注意的是,Encoding 類別設計用來處理文字資料,而非任意的二進位內容。如果要將任意二進位資料轉成字串儲存 (例如將圖片或加密資料以字串表示),不應直接使用文字編碼 (否則可能造成資料遺失或損毀),而應採用例如 Base64 等專門的編碼方式。

碼點 (code point) 與 碼元 (code unit)

碼點 (code point) 與 碼元 (code unit) 是字元編碼領域中的兩個重要概念,它們在字元表示和儲存中扮演著不同的角色。

  • 碼點 (Code point)

    是指在編碼字元集中,每個字元所對應的唯一數值。例如,在 Unicode 中,字元 "A" 的碼點是 U+0041

  • 碼元 (code unit)

    則是指在特定編碼形式中,用於表示碼點的最小資料單位。

    不同的編碼方式,其碼元的大小可能不同。例如,在 UTF-8 編碼中,每個碼元為 8 位元;而在 UTF-16 編碼中,每個碼元為 16 位元。

簡單來說,碼點 (code point) 是對應於字元的抽象數值,而 碼元 (code unit) 則是具體編碼方式中用來表示這些碼點的實際資料單位

常見的編碼類型

常見的文字編碼方式有許多種,各自適用於不同情境。以下列出幾種常用編碼及其特性:

  • UTF-8

    一種 Unicode (萬國碼) 的實作,使用可變長度位元組序列來編碼字元,英文字母等基本字元只需 1 個位元組,歐洲及中東字元通常 2 個位元組,東亞文字可能需要 3~4 個位元組。

    UTF-8 能表示所有 Unicode 字元,具有良好的儲存效率和廣泛的相容性,因此成為網際網路及跨平台傳輸的事實標準。以 UTF-8 編碼的文字沒有語系限制,非常適合多語言環境。

  • UTF-16

    Unicode 的 16 位元編碼形式,每個字元通常使用 2 個位元組表示。

    依照位元序不同可分為 UTF-16 LE (Little Endian,小端序) 與 UTF-16 BE (Big Endian,大端序)。

    在 Windows 等主流平台上常用 UTF-16 LE (.NET 的 Encoding.Unicode 屬性即代表 UTF-16 LE 編碼。

    UTF-16 同樣可表示全部 Unicode 字元,但因固定每字元至少 2 位元組,對以拉丁字母為主的內容來說檔案體積會比 UTF-8 大一些。

    UTF-16 BE 則主要在某些大型主機特定系統使用,使用時須注意大小端序需與資料對應。

  • UTF-32

    Unicode 的 32 位元編碼形式,每個字元固定使用 4 個位元組表示 (可直接對應 Unicode 碼點)。

    Encoding.UTF32 提供了 UTF-32 LE 編碼(以及透過建構函式可建立 UTF-32 BE 編碼。

    UTF-32 因為每個字元占用空間大,一般較少用於儲存或傳輸,但在需要固定長度表示字元的場景下可能會用到。

  • ASCII

    一種早期的 7 位元編碼,只定義了 128 個字元 (包括英文字母、數字、標點符號和控制字元)。

    在 .NET 中可透過 Encoding.ASCII 使用此編碼。ASCII 編碼不支援中文、日文等任何超出 U+007F 的字元,所以僅適用於純英文環境

    如內容包含非 ASCII 字元,使用 ASCII 編碼將無法正確表示,未支援的字元通常會被遺失替換成問號

  • ANSI / 碼頁 (Code Page)

    ANSI 並非一種單一定義的編碼,而是對各地區預設碼頁的統稱。

    例如 美國 / 西歐 Windows 的預設 ANSI 編碼是 Windows-1252 (類似 ISO-8859-1 西歐編碼),繁體中文 Windows 的 ANSI 編碼則是 Big5,日文 Windows 則是 Shift-JIS 等。

    這類編碼大多採用單/雙位元組混合的形式表示字元,也就是所謂「延伸 ASCII」或傳統碼頁 (Code Page) 編碼。

    下面分別介紹幾個代表:

    • Big5

      早期繁體中文地區使用的主要編碼,屬於雙位元組編碼。

      Big5 由台灣五大電腦公司在 1980 年代制定,名稱即取自「五大碼」 (Big5 - Wikipedia)。

      Big5 編碼可涵蓋常用的繁體中文字以及基本的拉丁字母和符號,在台灣、香港、澳門曾廣泛用於文件、郵件和 Windows 系統預設語系。現今 UTF-8 普及後,Big5 主要在舊系統相容或歷史資料中會見到。

    • Shift-JIS

      日本使用的傳統編碼 (又稱 SJIS),也是一種可變長度的雙位元組編碼。

      Shift-JIS 由日本的 ASCII 公司和微軟共同制定,結合了 JIS 標準的單位元假名與雙位元漢字 (Shift JIS - Wikipedia)。

      在 Unicode 普及前,Shift-JIS 一直是日文 Windows 和網站的主要編碼 (Shift JIS - Wikipedia)。現今在日文環境中 UTF-8 逐漸成為主流,不過 Shift-JIS 仍可在某些舊網站、電子郵件或檔案中遇到。

      在 .NET 中,Shift-JIS 對應的碼頁編號是 932 (Windows-31J 是其超集)。若需處理日文舊系統資料,可能需要用到此編碼。

除了上述之外,還有例如 ISO-8859 系列 (拉丁語系各國的單位元編碼) 以及其他國家 / 地區的本地編碼 (如簡體中文GB2312/GBK/GB18030 等)。但整體趨勢是 Unicode已成主流,建議在可能情況下都優先使用 Unicode 編碼 (UTF-8/UTF-16) 處理文字,以避免不同編碼轉換造成的困擾。

如何取得與使用編碼

在 .NET 中,可以透過多種方式取得對應的 Encoding 物件:

  • 靜態屬性

    Encoding 類別直接提供了幾個常用編碼的靜態屬性,例如 Encoding.UTF8Encoding.Unicode(UTF-16 LE)、Encoding.BigEndianUnicode(UTF-16 BE)、Encoding.ASCII 等,可直接使用這些屬性取得對應的編碼實例。

    這些屬性涵蓋了 ASCII 和主要的 Unicode 編碼,通常是最常用的選擇。

  • 類別建構函式

    對於 ASCII、UTF7、UTF8、UTF16、UTF32 等,也可以直接呼叫相應的編碼類別的建構子來建立實例,例如 new UTF8Encoding()new UnicodeEncoding() 等。

    靜態屬性其實已提供單例實例(Singleton),一般不需要重複建立,但透過建構函式可以設定一些參數 (例如是否提供 BOM 或錯誤回退行為等)。

  • GetEncoding 方法

    Encoding.GetEncoding(name或codePage) 是一種更通用的取得編碼實例的方法。它允許你以編碼名稱 (如 "UTF-8", "Big5", "Shift_JIS", "windows-1252" 等) 或碼頁編號 (code page,如 950、932 等) 來取得對應的編碼。例如:

    Encoding utf8 = Encoding.GetEncoding("UTF-8");
    Encoding big5 = Encoding.GetEncoding("Big5");
    Encoding big5 = Encoding.GetEncoding(950);      // 950 是 Big5 碼頁
    Encoding sjis = Encoding.GetEncoding("Shift_JIS");
    

    然而,需要注意並非所有編碼在 .NET Core/.NET 5+ 中都直接可用。在 .NET Framework 上,GetEncoding 幾乎可以取得任何已安裝於 Windows 的碼頁編碼。但在 .NET Core 平台,出於精簡預設僅內建支援少數幾種編碼 (主要是 Unicode 系列和 latin-1 等)。

    例如:如果直接在 .NET Core 3.1 或 .NET 5 的程式中呼叫 Encoding.GetEncoding("Big5"),會拋出例外,訊息提示「不支援的編碼」或建議使用 Encoding.RegisterProvider 方法。這是因為 .NET Core 預設不包含 Big5、Shift-JIS 等傳統碼頁。

    Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
    
  • Encoding.GetEncodings

    如果想查看目前環境支援哪些編碼,可以呼叫 Encoding.GetEncodings() 列出所有可用的編碼資訊。

    但如上所述,在未額外設定下 .NET Core 列出的清單會相對有限。

支援額外編碼 (安裝 CodePagesEncodingProvider)

為了在 .NET Core / .NET 5+ 中使用 Big5、Shift-JIS、Windows-125x 等額外的編碼,需要引入 CodePagesEncodingProvider。具體作法是:

  1. 安裝套件

    在專案中安裝 NuGet 套件 System.Text.Encoding.CodePages。此套件包含了對各種碼頁的支援資料。

    How to read Windows-1252 encoded files with .NETCore and .NET5+ | Nicola Iarocci

  2. 註冊提供者

    在程式啟動時 (例如 Main 方法或應用程式初始化時) 呼叫,只需要呼叫一次:

    Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
    

    這行程式會註冊一個編碼提供者(CodePagesEncodingProvider),讓 .NET Core 能夠使用 .NET Framework 提供的那些傳統碼頁編碼。註冊後,先前不支援的編碼就會被加入可用清單中。

  3. 取得編碼

    提供器註冊後,即可正常使用 Encoding.GetEncoding() 來取得所需的編碼物件,例如 Encoding.GetEncoding("Big5")Encoding.GetEncoding("Shift_JIS"),不再拋出不支援例外。之後便可像使用其他 Encoding 一樣讀寫轉換文字。

下面是完整的範例流程:假設需要讀取 Big5 編碼的檔案,可這樣撰寫程式碼:

using System.Text;

// 安裝了 System.Text.Encoding.CodePages 套件後:
using System.Text.Encoding.CodePages;

// ... 程式啟動時:
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

// 讀取 Big5 編碼的文字檔
Encoding big5 = Encoding.GetEncoding("Big5");
string content = File.ReadAllText("data_big5.txt", big5);

經過上述步驟,Encoding 類別就能支援大部分傳統編碼 (涵蓋各國碼頁),這對需要處理舊檔案或與非 Unicode 系統相容性時非常有用 (How to read Windows-1252 encoded files with .NETCore and .NET5+ | Nicola Iarocci)。請注意,註冊動作只需要執行一次 (通常在應用程式啟動時),一旦註冊,後續所有 GetEncoding 呼叫都能取得額外的編碼,不需要每次重複註冊提供者。

跨平台相容性

由於 .NET Core/.NET 5+ 是跨平台的運行時環境,在不同作業系統上的編碼支援略有差異,需要特別注意以下幾點:

  • 預設支援的編碼

    正如前面所述,.NET Core 為減少體積僅內建支援少數編碼,除了 Unicode 家族 (UTF-8/16/32) 和 ASCII 以及 ISO-8859-1 (碼頁 28591) 外,其他如 Big5、Shift-JIS、Windows-1252 等都不內建。相對地,傳統 .NET Framework (僅限 Windows) 則憑藉作業系統提供了全面的碼頁支援。

    因此,如果一段程式碼在 .NET Framework 上可以直接使用 Encoding.GetEncoding(1252) 取得 Windows-1252 編碼,在 .NET Core 上執行則需要先安裝套件並註冊提供者,否則會報錯「沒有可用的編碼」。

  • Encoding.Default

    在 .NET Framework 上,Encoding.Default 屬性會返回當前系統的 ANSI 編碼,例如美國英文系統通常是 Windows-1252,繁體中文系統則會是 Big5,依據作業系統的地區設定而定。

    但在 .NET Core/5+ 中,為了跨平台一致性,Encoding.Default 被固定定義為 UTF-8 編碼。也就是說,在 Linux、macOS 或 Windows 上,.NET Core 的 Default 都是 UTF-8,而不再是各自系統的本地碼頁,這點差異必須特別注意。

    因此,不要依賴 Encoding.Default 來猜測本地編碼,因為在新平台下它並不代表使用者環境的語系;若需要特定編碼,應明確指定。例如,以下程式在 .NET Framework 和 .NET 6 上會有不同結果:

    Console.WriteLine(Encoding.Default.BodyName);
    

    在 .NET Framework (假設系統地區為台灣) 可能輸出 Big5,但在 .NET 6 不論系統語言都會輸出 utf-8。這項差異提醒我們,在跨平台程式中最好自行指定編碼,而非使用預設值。

  • 不同 OS 的編碼行為

    由於各作業系統底層對編碼支援不同,某些情境下也要注意。例如在 Linux 上跑 .NET,如果沒有透過 CodePagesEncodingProvider,引入的碼頁資料其實來自 .NET 的內建對照表,而非作業系統。

    所以一旦註冊了 CodePagesEncodingProvider,編碼行為在各平台應該是一致的 —— 因為都使用相同的對應表。

    不過仍建議盡量使用 Unicode 等通用編碼來進行跨平台文字交換,因為這樣最簡單也最不易出錯。如果不得不使用如 Big5 等區域性編碼,也要確保所有執行環境都有安裝相應提供器,並在測試時於不同 OS 上驗證文字顯示正確無誤。

總之,在開發跨平台 .NET 應用時,應養成明確指定編碼的習慣,並利用 Unicode 編碼來統一處理資料,只有在需要相容時才使用特定碼頁且正確載入支援。這能最大程度確保 Windows、Linux、Mac 上輸入輸出文字的一致性。

實作範例

下面透過幾個常見的場景,說明如何正確使用 Encoding 類別來讀寫資料,避免發生亂碼或編碼錯誤。

  • 讀取與寫入文字檔案

    日常開發中經常需要讀寫文字檔,正確處理編碼能避免產生亂碼的問題。例如,我們有一個文字檔,其實內容是以 Big5 編碼儲存的繁體中文,但若程式錯誤地以 UTF-8 去解讀它,就可能出現「鬼畫符」般的亂碼。

    Image

    如上圖所示,當以錯誤的編碼解讀文字時,會出現無意義的亂碼。展示了原本以 UTF-8 編碼的日文維基百科頁面,如果錯用 Windows-1252 編碼去解讀時的情況 —— 文本變成一連串毫無意義的符號。類似地,如果讀取檔案時編碼搞錯,也會得到亂七八糟的內容甚至問號。同理,寫出檔案時若編碼不對,其他應用開啟時也可能是亂碼。

    避免亂碼的關鍵在於讀寫雙方約定使用相同的編碼。例如,若知道檔案是 Big5 編碼,就要用 Big5 去讀取。在 .NET 中,可以使用 File.ReadAllText/File.WriteAllText 等方法並指定編碼參數,或使用 StreamReader/StreamWriter 來完成。例如:

    // 讀取 Big5 編碼的檔案
    Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); // 確保支援Big5
    
    string txt = File.ReadAllText("article.txt", Encoding.GetEncoding("Big5"));
    
    // 寫出 UTF-8 編碼的檔案
    string output = "你好,Hello!";
    File.WriteAllText("output.txt", output, Encoding.UTF8);
    

    上例中,我們在讀取前註冊了編碼提供器以支援 Big5,然後明確指定使用 Big5 去解碼檔案內容。寫出檔案時則指定以 UTF-8 編碼輸出字串。如此一來,就不會發生亂碼問題。

    使用 StreamReader/StreamWriter 時情況類似:

    // 使用 StreamReader 讀取 (假設檔案為 Shift_JIS 編碼)
    Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
    using var reader = new StreamReader("data_sjis.txt", Encoding.GetEncoding("Shift_JIS"));
    string line;
    while((line = reader.ReadLine()) != null) {
        // 處理每行文字
    }
    
    // 使用 StreamWriter 寫入 (例如寫入 UTF-16 編碼檔案)
    using var writer = new StreamWriter("log_unicode.txt", false, Encoding.Unicode);
    writer.WriteLine("記錄一條日誌...");
    

    特別地,StreamReader未指定編碼時,.NET Core 中預設會以 UTF-8 嘗試解碼 (並自動偵測 UTF-8/UTF-16 的 BOM)。如果檔案本身不是 UTF-8,這時就可能解讀錯誤。因此對於沒有 BOM 且非 UTF-8 編碼的檔案,更要明確指定正確的編碼。總之,讀寫檔案最好始終指定編碼,以免依賴預設值而發生預料外的結果。

  • 網路請求與回應 (API)

    在進行 HTTP 通訊或呼叫 API 時,也經常涉及文字的編碼。例如發送一個 JSON 請求,預設應使用 UTF-8 編碼內容;又或者請求一個網頁,需根據對方提供的編碼來解碼回應。

    請求方面:使用 .NET 的 HttpClient 時,可以透過 StringContent 指定內容的編碼和媒體類型。例如:

    var json = "{\"name\":\"測試\"}";
    var content = new StringContent(json, Encoding.UTF8, "application/json");
    await httpClient.PostAsync(apiUrl, content);
    

    上述程式將確保發出的 HTTP 請求主體使用 UTF-8 編碼,並告知伺服器內容類型為 JSON (包含 charset=UTF-8)。大部分網路 API 都預設採用 UTF-8 傳輸文字資料,如果需要其他編碼 (較少見),也應在此明確指定,例如:

    var json = "{\"name\":\"測試\"}";
    var content = new StringContent(json, Encoding.GetEncoding("Big5"), "application/json");
    await httpClient.PostAsync(apiUrl, content);
    

    回應方面:接收資料時要依 Content-Type 的字元編碼來解碼。比如使用 HttpClient 時,若知道回應是 UTF-8,可直接用 response.Content.ReadAsStringAsync() 取得字串 (內部會據 content-type 的 charset 解碼)。但如果回應是其他編碼 (假設一個老舊服務回傳 Big5 編碼的 HTML),可以這樣處理:

    byte[] bytes = await httpClient.GetByteArrayAsync(url);
    Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
    string html = Encoding.GetEncoding("Big5").GetString(bytes);
    

    先取得位元組陣列(byte[]),再用正確的編碼解碼成字串。

    針對使用 ASP.NET 建構的 Web 應用,框架本身也提供編碼設定的屬性。例如可以將 HttpResponse.ContentEncoding 設定為與請求相同的編碼,確保送回給用戶端的頁面使用的是它可以正確顯示的編碼。

    通常情況下,目前瀏覽器與服務端通訊都以 UTF-8 為主,但如果你的網站需要支持特定語系 (如繁中 zh-TW 頁面),務必在 Response 設定對應的 Encoding,並在 HTML <meta charset> 或 HTTP Header 中宣告編碼,讓瀏覽器正確渲染。

  • 資料庫存取

    資料庫的文字處理雖然大多對開發者透明,但仍有一些最佳做法來避免編碼問題。現代關聯式資料庫 (如 SQL Server、MySQL、PostgreSQL 等) 通常都支援 Unicode 欄位類型 (例如 NVARCHAR/NCHAR 等類型),建議優先使用這些欄位儲存文字,以便資料庫和 .NET 應用之間以 Unicode 字串直接交換資料,而不需考慮中間編碼轉換。

    如果你的資料庫欄位使用非 Unicode 編碼 (例如某歷史系統的資料庫使用 Big5 編碼儲存文字),那在存取時就要特別小心一致性。一般透過 ADO.NET 提供者 (例如 System.Data.SqlClient 或 MySQL Connector 等) 查詢資料時,驅動程式會參考連線字元集資料庫的編碼設定,自動將資料轉為 .NET 的 Unicode 字串。然而前提是連線字串資料庫屬性要正確配置。例如 MySQL 連線字串通常需要指定 Charset=utf8 或其他編碼,否則可能出現亂碼。

    在撰寫程式時,儘量讓資料庫傳回 string 而非 byte[] 來避免手動解碼。如果不得已需要從資料庫取得位元組再轉字串,一定要知道那些位元組代表的編碼。例如:

    // dataReader 是從資料庫讀取出來的 DataReader
    byte[] blob = (byte[])dataReader["Name_Bytes"];
    string name = Encoding.GetEncoding("Big5").GetString(blob);
    

    如上,明確以 Big5 解碼才不會造成誤解碼。如果編碼不對,可能出現亂碼或問號。寫入時亦同理,應將字串轉成正確編碼的位元組再存入。如果資料庫與應用都使用 UTF-8/Unicode,則這方面的煩惱最少。因此最好的辦法還是保持端到端皆使用 Unicode,在資料庫層面就避免資訊遺失的風險。

跨平台文字處理

在跨平台環境下 (例如資料在 Windows 上產生,拿到 Linux 上處理),更需要小心編碼的一致。建議遵循以下原則:

  • 使用通用編碼交換資料

    如果可能,儘量使用 UTF-8 編碼來儲存和交換文字資料,因為它在各平台都是標準支援的 (.NET Core 預設就是 UTF-8)。

    例如,你在 Windows 上產生一個 UTF-8 編碼的檔案,那麼在 Linux 或 macOS 上用 .NET 讀取時,不用做任何特殊設定就能正確解碼。

  • 夾帶編碼訊息

    在需要傳輸文字的場合,最好能夠隨資料附帶編碼說明。例如 XML/HTML 檔案應在檔頭宣告 encoding,或純文字檔可以考慮加上 BOM (Byte Order Mark) 來提示。

    雖然 BOM 不是強制的,但對於 UTF-8/UTF-16 檔案,Windows 上的某些軟體 (如舊版記事本) 會依賴 BOM 判斷編碼。如果沒有 BOM,確保協定或文件格式有其他方式知會編碼,否則另一端可能用錯編碼打開導致亂碼。

  • 碼頁編碼的跨平台

    如果必須交換非 Unicode 編碼的資料 (例如老系統產生了 Big5 檔案,要在 Linux .NET 上處理),務必在 .NET Core 環境安裝 System.Text.Encoding.CodePagesRegisterProvider,否則程式無法識別 Big5 編碼。

    好在只要提供器註冊了,在 Linux 下處理 Big5 與在 Windows 下效果是一樣的。因此,只要我們在跨平台程式中明確指定使用何種編碼讀寫,就能確保不同系統下結果相同。

簡而言之,跨平台時將文字以 Unicode (如 UTF-8) 形式交流是最穩妥的做法。如果必須使用其他編碼,也要讓接收方清楚知道使用何種編碼並具備相應支援。保持這種約定一致,才能避免「平台 A 寫的文件到平台 B 變亂碼」的窘境。

串流處理 (Stream)

除了檔案與網路,應用程式內也可能需要在串流中讀寫文字,例如讀寫 MemoryStreamNetworkStream 等。使用 StreamReaderStreamWriter 配合 Encoding 可以方便地處理串流中的文字內容。用法上與檔案讀寫類似,只是改為對接任意的 Stream。

例如,假設有一段程式要經由網路串流傳送指令,接收端約定用 ASCII 編碼解析,那發送端可以這樣寫:

using NetworkStream ns = tcpClient.GetStream();
using StreamWriter writer = new StreamWriter(ns, Encoding.ASCII);
writer.Write("HELLO\n");
writer.Flush();

這會將字串「HELLO」按 ASCII 編碼寫入網路串流。接收端只要也按 ASCII 讀取就能得到正確的字元。再如,接收端想讀取 UTF-8 的訊息:

using StreamReader reader = new StreamReader(ns, Encoding.UTF8);
string message = reader.ReadLine();

StreamReader/Writer 建構子允許我們指定編碼,還能指定是否需要偵測 BOM、緩衝大小等。若未指定編碼,StreamReader 預設行為與前述檔案情況一樣會當作 UTF-8 (含 BOM 偵測)。所以除非可以確定串流資料一定是 UTF-8,否則應明確給出編碼參數。

處理串流時,最好也考慮緩衝與 Flush 問題,例如上例中在寫入後呼叫了 Flush(),確保資料立即從緩衝送出。另外,如果串流是雙向或長連線的,通信雙方應先約定好使用何種文字編碼來解析彼此的訊息,必要時可在協定中增加檔頭來協調 (類似 HTTP 的 Content-Type)。總之原則和先前一致:雙方編碼設定相符,傳輸內容才能正確還原。

最佳實踐與建議

綜合以上內容,以下是關於文字編碼在 .NET Core (含 .NET 5+) 開發中的一些最佳實踐建議:

  • 預設使用 Unicode 編碼

    優先選擇 UTF-8 或 UTF-16 作為應用程式處理和儲存文字的編碼。UTF-8 對英文內容幾乎沒有體積負擔,對全球語言都有支援,而且效能非常高。除非有特殊需求,UTF-8 通常是讀寫檔案、網路通訊的首選編碼。UTF-16 在 .NET 字串內部使用,可在需要固定雙位元組 (例如與某些 Windows API 互動) 時考慮。不建議使用 UTF-7 (已淘汰,安全性差) 等已過時的編碼。

  • 減少不必要的編碼轉換

    既然 .NET 字串本身就是 Unicode,應儘量讓資料以 Unicode 形式進出系統,避免在不同編碼之間來回轉換。比如從資料庫讀出 UTF-8 bytes -> 轉字串 -> 再轉成另一編碼 bytes 這種流程能免則免,直接在資料庫使用 Unicode 欄位即可省去轉換環節,減少出錯機率和效能開銷。

  • 謹慎使用 ASCII/Ansi 編碼

    僅在明確確認資料範圍時才使用 ASCII 或區域性碼頁編碼。例如只包含英數字元的通訊協定可使用 ASCII,但如果稍有可能出現非 ASCII 字元,應改用 UTF-8 以免資訊遺失。

    實務中常見錯誤是誤用 ASCII 導致非英文字元全部變成問號 ?。同理,除非處理特定舊系統資料,否則很少需要硬性使用 Big5、Shift-JIS 等編碼;即便需要,也應全程測試確保沒有字元轉換錯誤。

  • 永遠明確指定編碼

    無論是讀檔、寫檔,還是串流通訊,都養成指定 Encoding 的習慣,不要依賴預設值。這在跨區域、跨平台時尤為重要。例如 File.ReadAllText(path) 在 .NET Framework 上會用系統預設 ANSI 編碼讀檔案,在 .NET Core 則會用 UTF-8,行為不一致,而使用 File.ReadAllText(path, Encoding.X) 則可避免這種差異。

    同樣,StreamReader/Writer 請提供 Encoding 參數。總之,將編碼視為檔案格式/通訊協定的一部分,跟檔名、副檔名一樣需要被明確管理。

  • 啟用額外編碼提供器

    對於需要處理非 Unicode 編碼資料的應用,例如批次轉換老舊文件編碼、讀取舊系統輸出報表等,請務必在程式初始化時呼叫 Encoding.RegisterProvider(CodePagesEncodingProvider.Instance) 並隨專案一起部署 System.Text.Encoding.CodePages 套件。這樣可以避免某些環境下 GetEncoding 取不到所需編碼的問題。

    一旦註冊,一些碼頁編碼就能跨平台使用,如 Big5 在 Linux 上也能正確轉換。

  • 注意 Fallback (後備機制)

    Encoding 在編碼 / 解碼遇到無法對應的字元時,預設會以「?」取代未知字元。雖然這可避免拋出錯誤中斷程式,但有時可能掩蓋資料問題。如果對資料完整性要求很高,可以考慮設定 EncoderFallback/DecoderFallback 為 ExceptionFallback,使其在遇到無法處理的字元時拋出例外,方便偵錯。此外也可客製替代字元。但大多數情況下,只要源和目標編碼對應正確,不應該出現亂碼或無法對應的情況。

  • 測試與文件

    最後,針對編碼相關的功能,多在不同語言環境下測試 (例如切換系統語言或在不同 OS 上測試),以發現潛在的問題。並且在文件或程式碼註解中標明資料的編碼假設,方便日後維護者了解。舉例而言,若你的應用匯出一份報表檔案是 Big5 編碼,務必在說明檔提及,否則接手的人可能困惑為何不用 UTF-8。

總而言之,文字編碼雖然看不見但無處不在。良好的習慣是:盡可能以 Unicode 處理文字,全程保持編碼一致,在需要轉換時使用 .NET 提供的 Encoding 工具類別並小心核對。

遵循這些最佳實踐,可以大幅降低遇到亂碼的機率,讓你的 .NET 應用在任何地區任何平台都能正確地讀寫文字資料。

相關連結

留言評論