[c#]如何驗證byte[]是否是UTF-8編碼

0x00 答案

下面是C#判斷給定的byte[]是否是指定UTF-8編碼的代碼。代碼後有關於字符編碼的一些知識,有興趣可以移步

    private static byte UTF8CharacterMask1Byte = 0b1000_0000;
    private static byte Valid1Byte = 0b0000_0000;//0b0xxx_xxxx

    private static byte UTF8CharacterMask2Byte = 0b1110_0000;
    private static byte Valid2Byte = 0b1100_0000;//0b110x_xxxx

    private static byte UTF8CharacterMask3Byte = 0b1111_0000;
    private static byte Valid3Byte = 0b1110_0000;//0b1110_xxxx

    private static byte UTF8CharacterMask4Byte = 0b1111_1000;
    private static byte Valid4Byte = 0b1111_0000;//0b1111_0xxx

    private static byte UTF8CharacterMaskForExtraByte = 0b1100_0000;
    private static byte ValidExtraByte = 0b1000_0000;//0b10xx_xxxx


    public static bool IsValidUTF8ByteArray(byte[] bytes)
    {
        short extraByteCount = 0;

        foreach (byte bt in bytes)
        {

            if (extraByteCount > 0)
            {
                extraByteCount--;

                // Extra Byte Pattern.
                if ((bt & UTF8CharacterMaskForExtraByte) != ValidExtraByte)
                    return false;
                continue;
            }
            else
            {
                // 1 Byte Pattern.
                if ((bt & UTF8CharacterMask1Byte) == Valid1Byte)
                {
                    continue;
                }

                // 2 Bytes Pattern.
                if ((bt & UTF8CharacterMask2Byte) == Valid2Byte)
                {
                    extraByteCount = 1;
                    continue;
                }

                // 3 Bytes Pattern.
                if ((bt & UTF8CharacterMask3Byte) == Valid3Byte)
                {
                    extraByteCount = 2;
                    continue;
                }

                // 4 Bytes Pattern.
                if ((bt & UTF8CharacterMask4Byte) == Valid4Byte)
                {
                    extraByteCount = 3;
                    continue;
                }

                // invalid UTF8-Bytes.
                return false;
            }
        }
        
        return extraByteCount == 0;
    }

0x01 UTF-8編碼和Unicode字符集

討論字符編碼的時候總是容易混淆Unicode,UTF-8,UTF-16,UTF-32等概念。
所以在正式開始之前,先確定一下。

什麼是字符集,什麼是編碼(Encoding)。兩個關鍵概念。

1. 字符集(Charset or Character set):
按字面理解,就是字符的集合。不過沒有想象那麼簡單,在RFC2278裏"Charset"有如下定義

2.3 Charset
The term “charset” (see historical note below) is used here to refer
to a method of converting a sequence of octets into a sequence of
characters
. This conversion may also optionally produce additional
control information such as directionality indicators.

如果用C#的Method來舉例子說的話,Charset就像下面這個方法用一樣。用於轉換byte[]到char[]

public char[] Charset(byte[]);

這個定義和我們想象中的一個字符一個編號的字符集差得很遠,不過這個是標準裏的定義,也有過爭論。而RFC2278裏提到的另一個術語Coded Character Set(CCS)就和我們所常認爲的字符集的意思能吻合。

2.4. Coded Character Set
A Coded Character Set (CCS) is a mapping from a set of abstract
characters to a set of integers
. Examples of coded character sets are
ISO 10646 [ISO-10646], US-ASCII [US-ASCII], and the ISO-8859 series
[ISO-8859].

例如US-ASCII定義了128個字符到0-127數字的映射。
映射字符到數字,bingo了!我們常說的ASCII、Unicode等其實都是Coded Character Set。而非Charset,不過非學術上已經把charset和coded character set已經混爲一談了,以下爲了討論方便還是用字符集來指代Coded Character Set。(這倆關鍵翻譯成中文都是字符集…筆者真心搞不懂Charset和Character Set的差別爲毛這麼大。如果有知道的朋友剋呀留言告訴筆者。)

2. 編碼(Encoding):

作爲常被我們唸叨的編碼,其實是一個信息轉換的過程,視網膜會對進入的光線進行編碼然後通過神經送入大腦,大腦然後對其解碼。這便是我們看到世界的過程。

而計算機(電腦)也差不多,從別人手機上發送的字符串“123?“編碼成二進制碼後經過網絡傳輸,發送到另一個設備上,這個設備的程序把二進制碼解碼後拿到同樣的“123?“然後畫在屏幕上。

而從"123?" → 二進制碼的過程叫做編碼。對於字符編碼,它被叫做Character Encoding (link to wiki)

對於一個字符‘?‘要編碼到二進制碼,分爲三個步驟,Unicode 標準定義[4]

  1. Character → Code Point (Through Coded Character Set)
  2. Code Point -> Code Units(Through Character Encoding Form)
  3. Code Units -> Sequence of bytes (Through Character Encoding Scheme)

我們用字符集Unicode,UTF-8編碼來看一下笑哭這個表情如何被編碼爲UTF-8二進制碼的。

1. Unicode字符集: Character -> Code Point
    '?'  → 0x1F602 (Decimal: 128514)
2. UTF -8 CEF(Character Encoding Form): Code Point -> Code Units
    0x1F602  → 0xF0 0x9F 0x98 0x82  // 4個Code Units,UTF-8每個Code Unit爲8-bits	    
3. UTF-8 CES(Character Encoding Scheme): Code Units -> Bytes 
    0xF0 0x9F 0x98 0x82  → 0xF0 0x9F 0x98 0x82  
    // 因爲UTF-8的Code Unit本身就是8位,所以這一步實際上在UTF-8是省略的,但是在UTF-16裏,有BE(Big Endian)和LE(Little Endian)的區別。
    // 所以第三步的時候會把無序的Code Units轉換到指定順序的Bytes。

0x02 總結

  1. 我們常說的字符集,一般指的的Coded Character Set 而非 Charset。
  2. Unicode是一種Coded Character Set。
  3. 想註冊Character Set可以參考RFC 2278 - IANA Charset Registration Procedures
  4. 已有的Character Sets可以參考Character Sets - IANA
  5. 對於一個字符轉換到二進制碼,在Unicode標準裏,被分成了三步。(字符→Code Point(數字)→Code Units->Byte Sequence)
  6. 5的每一步都有設計各自的專有名詞(CCS、CEF和CES)
  7. 對於UTF-8編碼來說,沒有第三步,因爲UTF-8是面向字節編碼的。
  8. 對於UTF-16、UTF-32編碼來說,第三步的關鍵操作是指定BOM(Byte Order Mask),來告訴接收方編碼好的UTF-16、UTF-32的Bytes是Big Endion(BE)的還是Little Endian(LE)的。

以上,希望你能有所收穫。

參考文獻:
[1] “RFC 3629 - UTF-8, a transformation format of ISO 10646”, Internet Engineering Task Force.(2019/03/23) Retrieved https://tools.ietf.org/html/rfc3629
[2] “Character Sets”, IANA.(2019/03/23) Retrieved http://www.iana.org/assignments/character-sets/character-sets.xhtml
[3] “RFC 2278 - IANA Charset Registration Procedures”, Internet Engineering Task Force.(2019/03/23) Retrieved https://www.ietf.org/rfc/rfc2278.txt
[4] “Unicode Technical Report #17 - UNICODE CHARACTER ENCODING MODEL”, http://unicode.org/reports/tr17/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章