[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/

发布了24 篇原创文章 · 获赞 24 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/ToraNe/article/details/88769264
今日推荐