弄清UTF8和Unicode


長期以來,一直對字符串編碼認識比較粗略,認為支持"特殊字符"編碼就是Unicode。當然,.NET平台上很少需要考慮這類問題,但搞清一些基本概念還是很有好處的。

Unicode這個詞,首先是國際標准的通用字符集(UCS)名稱,囊括了漢語八國聯軍火星文等各種文字。這是一個面向用戶的字符編碼標准。其他的編碼標准如GB2132,BIG5什么的都是Unicode標准之前的老黃歷了,彼此間,與現代系統間各種不兼容。

而.Net中的UnicodeEncoding類,是實現Unicode字符集的一種編碼方式,將一個字符轉換成字節形式。其名稱容易引起qi義,其實這個編碼方式通用名稱(在其他編程語言中)叫UTF16,此外流行的兩種還有UTF8和UTF32,UTF8更常用。UTF是UCS Transformation Format的縮寫。

這里也不貼具體的轉換方式了,有興趣可以到百科上去看。關鍵要知道.NET中UnicodeEncoding和UTF8Encoding兩個類主要區別,前者每個字符一律都是兩個字節,后者可能是1~3個字節:英文字母和標點1個,漢字占3個。顯然這兩種方式也不是兼容的,各有長短。

然而我們打開絕大多數文本文件,不管哪種編碼,都可以正常顯示所有內容。那么系統是如何判斷文件的編碼呢?我們可以做一個測試。

            var filename = "encoding-test";
            var text = "變!";
            var encodingInFile = Encoding.Unicode;
            var encodingToCompare = Encoding.UTF8;

            //UTF16 encoding
            Console.WriteLine("Saved with UTF16 Encoding:");
            File.WriteAllText(filename, text, encodingInFile);

            Console.WriteLine("Read without Encoding: {0}",
                File.ReadAllText(filename));
            Console.WriteLine("Read with Encoding of File: {0}",
                File.ReadAllText(filename, encodingInFile));
            Console.WriteLine("Read with Another Encoding: {0}",
                File.ReadAllText(filename, encodingToCompare));
            Console.WriteLine();

 

 可看到,無論是否指定,指定了何種編碼去讀取文本文件,都不影響系統識別正確的編碼,輸出結果都是"變!"。寫入文件的Encoding換成UTF8等也一樣,只要支持Unicode就可以。

但我們從字節上分析,就可以發現其中的區別,接着上面的代碼,測試如下:

            var bytes = encodingInFile.GetBytes(text);
            Console.WriteLine("Bytes from Text: {0}",
                String.Join(" ", bytes.Select(b => b.ToString("X"))));
            Console.WriteLine("Text from Bytes with File Encoding: {0}",
                encodingInFile.GetString(bytes));
            Console.WriteLine("Text from Bytes with Another Encoding: {0}",
                encodingToCompare.GetString(bytes));

            Console.WriteLine();
            bytes = File.ReadAllBytes(filename);
            Console.WriteLine("Bytes from File: {0}",
                String.Join(" ", bytes.Select(b => b.ToString("X"))));
            Console.WriteLine("Text from Bytes with File Encoding: {0}",
                encodingInFile.GetString(bytes));
            Console.WriteLine("Text from Bytes with Another Encoding: {0}",
                encodingToCompare.GetString(bytes));

 輸出結果將是:

Bytes from Text: D8 53 21 0
Text from Bytes with File Encoding: 變!
Text from Bytes with Another Encoding: ?S!

Bytes from File: FF FE D8 53 21 0
Text from Bytes with File Encoding: ?變!
Text from Bytes with Another Encoding: ???S!

我們可以看到兩點,一是字節數組轉化字符串必須指定正確的Encoding;二是在文本文件中,系統在寫入時多加了兩個字節 FF FE,這就是系統能識別文件編碼的關鍵。

這多余的兩個字節,就是Unicode標准建議的用BOM(Byte Order Mark)。在寫入傳輸表示文本的字節,先傳輸被作為BOM的字節以表明編碼。對於UTF16或是UnicodeEncoding類,是FF FFE;對於UTF8則是EF BB BF,可以自己試一下。

所以在一般情況下,讀取文本文件根本不必指定Encoding,必須指定Encoding是處理異常情況:該文件沒有正確的保存,比如直接寫入字節而不是文本,可能還有不少軟件或系統會這么干,這樣很容易造成讀寫錯誤而出現亂碼。

要指出Excel就是這種我行我素的程序,對於CSV文件,要是用戶想編輯輸入Unicode字符,它會強迫你轉換成xlsx格式,不然它連UTF8格式字節都不給你寫入,用戶重新打開后發現上次輸入的Unicode字符全變成了問號。

另一個最常見的亂碼原因是歷史造成的。Unicode出現前,中韓日各種字符集百花亂放。這些舊字符集編碼的文件,以及用了這種編碼的程序,任何Unicode方式解碼出來都是亂碼,就像用密碼加密一樣。微軟為解決這個問題,采取了頁轉換表作為過渡技術。在控制面板->區域語言選項->管理,可以找到“非Unicode程序中所使用的當前語言”,就是指定就種頁表的。可以從Encoding.GetEncoding(int)取到,參數936是簡體中文GBK,950是繁體中文BIG5。然而只能指定了一種非Unicode編碼,如果還有電腦上還有別的非Unicode編碼,那就只能任亂碼橫行了。

根本的解決之道,就是標准化,就是大家都用Unicode。至於選UTF8還是UTF16,看情況,對於中文文檔,UTF16可以每個字節省一個字節。而國際上通用UTF8,也是XML標准編碼。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM