Unicode 一个包含汉字的文件如何知道每个字符使用多少字节?

Unicode 一个包含汉字的文件如何知道每个字符使用多少字节?,unicode,encoding,cjk,Unicode,Encoding,Cjk,我读了乔尔的文章,但仍然不了解所有的细节。举个例子来说明我的问题。请查看以下文件: (来源:) 我在二进制编辑器中打开文件,仔细检查第一个汉字旁边的三个a中的最后一个: (来源:) 据乔尔说: 在UTF-8中,0-127之间的每个代码点都存储在一个字节中。只有128及以上的代码点使用2、3存储,事实上,最多使用6个字节 编辑是这样说的: E6(230)高于代码点128 因此,我将以下字节解释为2,3,实际上,最多6个字节 如果是,什么指示解释超过2字节?E6后面的字节如何表示这一点 我的汉字

我读了乔尔的文章,但仍然不了解所有的细节。举个例子来说明我的问题。请查看以下文件:


(来源:)

我在二进制编辑器中打开文件,仔细检查第一个汉字旁边的三个a中的最后一个:


(来源:)

据乔尔说:

在UTF-8中,0-127之间的每个代码点都存储在一个字节中。只有128及以上的代码点使用2、3存储,事实上,最多使用6个字节

编辑是这样说的:

  • E6(230)高于代码点128
  • 因此,我将以下字节解释为2,3,实际上,最多6个字节
  • 如果是,什么指示解释超过2字节?E6后面的字节如何表示这一点


    我的汉字是以2、3、4、5还是6字节的形式存储的?

    提示如下:

    在UTF-8中,0-127之间的每个代码点 存储在单个字节中。唯一代码 点128及以上使用 2,3,实际上,最多6个字节


    在127之前的每个代码点的顶部位都设置为零。因此,编辑器知道,如果遇到顶部位为1的字节,则它是多字节字符的开头。

    最高0x7ff的代码点存储为2个字节;最多0xffff为3个字节;其他的都是4字节。(从技术上讲,最高可达0x1FFFF,但Unicode中允许的最高代码点为0x10ffff。)

    解码时,多字节序列的第一个字节用于确定用于生成序列的字节数:

  • 110x xxxx
    =>2字节序列
  • 1110xxxx
    =>3字节序列
  • 1111 0xxx
    =>4字节序列

  • 序列中的所有后续字节必须符合
    10xx xxxx
    模式。

    这是UTF8编码的全部部分(这只是Unicode的一种编码方案)

    通过检查第一个字节可以计算出大小,如下所示:

    > utf8 f4 8a af 8d
    Input:      (4) f4 8a af 8d
       Becomes: (4) f4 8a af 8d
       Finally: U+10abcd, with length of 4
    
    > utf8 e6 be b3 0
    Input:      (4) e6 be b3 0
       Becomes: (4) e6 be b3 00
       Finally: U+6fb3, with length of 3
    
    > utf8 41 0 0 0
    Input:      (4) 41 0 0 0
       Becomes: (4) 41 00 00 00
       Finally: U+41, with length of 1
    
    > utf8 87 0 0 0
    Input:      (4) 87 0 0 0
       Becomes: (4) 87 00 00 00
    Error -2
    
    > utf8 f4 8a af ff
    Input:      (4) f4 8a af ff
       Becomes: (4) f4 8a af ff
    Error -3
    
    > utf8 c4 80 0 0
    Input:      (4) c4 80 0 0
       Becomes: (4) c4 80 00 00
       Finally: U+100, with length of 2
    
    • 如果它以位模式
      “10”(0x80-0xbf)
      开头,它不是序列的第一个字节,您应该备份,直到找到开头,任何以“0”或“11”开头的字节(感谢Jeffrey Hantin在注释中指出这一点)
    • 如果它以位模式
      “0”(0x00-0x7f)
      开头,则为1字节
    • 如果它以位模式
      “110”(0xc0-0xdf)
      开头,则为2个字节
    • 如果它以位模式“1110”(0xe0-0xef)开头,则为3个字节
    • 如果它以位模式“11110”(0xf0-0xf7)开头,则为4个字节
    我将复制显示这一点的表格,但原件在WikipediaUTF8页面上

    上表中的Unicode字符由以下位构成:

    000z-zzzz yyyy-yyyy xxxx-xxxx
    
    其中,
    z
    y
    位在未给定的位置被假定为零。某些字节被视为非法的起始字节,因为它们是:

    • 无用:以0xc0或0xc1开头的2字节序列实际上给出的代码点小于0x80,可以用1字节序列更好地表示
    • RFC3629用于U+10FFFF以上的4字节序列,或5字节和6字节序列。这些是字节0xf5到0xfd
    • 只是未使用:字节0xfe和0xff
    此外,多字节序列中不以位“10”开头的后续字节也是非法的

    作为一个例子,考虑序列[0xF4]、0x8a、0xAF、0x8d]。这是一个4字节序列,因为第一个字节位于0xf0和0xf7之间

        0xf4     0x8a     0xaf     0x8d
    = 11110100 10001010 10101111 10001101
           zzz   zzyyyy   yyyyxx   xxxxxx
    
    = 1 0000 1010 1011 1100 1101
      z zzzz yyyy yyyy xxxx xxxx
    
    = U+10ABCD
    
    对于第一个字节0xe6(长度=3)的特定查询,字节序列为:

        0xe6     0xbe     0xb3
    = 11100110 10111110 10110011
          yyyy   yyyyxx   xxxxxx
    
    = 01101111 10110011
      yyyyyyyy xxxxxxxx
    
    = U+6FB3
    
    如果您查找该代码,您将看到它是您在问题中遇到的代码:澳.

    为了展示解码是如何工作的,我回到了我的档案,找到了我的UTF8处理代码。我不得不对它进行一些变形,使其成为一个完整的程序,并且编码已被删除(因为问题实际上是关于解码的),因此我希望我没有在剪切和粘贴中引入任何错误:

    #include <stdio.h>
    #include <string.h>
    
    #define UTF8ERR_TOOSHORT -1
    #define UTF8ERR_BADSTART -2
    #define UTF8ERR_BADSUBSQ -3
    typedef unsigned char uchar;
    
    static int getUtf8 (uchar *pBytes, int *pLen) {
        if (*pLen < 1) return UTF8ERR_TOOSHORT;
    
        /* 1-byte sequence */
        if (pBytes[0] <= 0x7f) {
            *pLen = 1;
            return pBytes[0];
        }
    
        /* Subsequent byte marker */
        if (pBytes[0] <= 0xbf) return UTF8ERR_BADSTART;
    
        /* 2-byte sequence */
        if ((pBytes[0] == 0xc0) || (pBytes[0] == 0xc1)) return UTF8ERR_BADSTART;
        if (pBytes[0] <= 0xdf) {
            if (*pLen < 2) return UTF8ERR_TOOSHORT;
            if ((pBytes[1] & 0xc0) != 0x80) return UTF8ERR_BADSUBSQ;
            *pLen = 2;
            return ((int)(pBytes[0] & 0x1f) << 6)
                | (pBytes[1] & 0x3f);
        }
    
        /* 3-byte sequence */
        if (pBytes[0] <= 0xef) {
            if (*pLen < 3) return UTF8ERR_TOOSHORT;
            if ((pBytes[1] & 0xc0) != 0x80) return UTF8ERR_BADSUBSQ;
            if ((pBytes[2] & 0xc0) != 0x80) return UTF8ERR_BADSUBSQ;
            *pLen = 3;
            return ((int)(pBytes[0] & 0x0f) << 12)
                | ((int)(pBytes[1] & 0x3f) << 6)
                | (pBytes[2] & 0x3f);
        }
    
        /* 4-byte sequence */
        if (pBytes[0] <= 0xf4) {
            if (*pLen < 4) return UTF8ERR_TOOSHORT;
            if ((pBytes[1] & 0xc0) != 0x80) return UTF8ERR_BADSUBSQ;
            if ((pBytes[2] & 0xc0) != 0x80) return UTF8ERR_BADSUBSQ;
            if ((pBytes[3] & 0xc0) != 0x80) return UTF8ERR_BADSUBSQ;
            *pLen = 4;
            return ((int)(pBytes[0] & 0x0f) << 18)
                | ((int)(pBytes[1] & 0x3f) << 12)
                | ((int)(pBytes[2] & 0x3f) << 6)
                | (pBytes[3] & 0x3f);
        }
    
        return UTF8ERR_BADSTART;
    }
    
    static uchar htoc (char *h) {
        uchar u = 0;
        while (*h != '\0') {
            if ((*h >= '0') && (*h <= '9'))
                u = ((u & 0x0f) << 4) + *h - '0';
            else
                if ((*h >= 'a') && (*h <= 'f'))
                    u = ((u & 0x0f) << 4) + *h + 10 - 'a';
                else
                    return 0;
            h++;
        }
        return u;
    }
    
    int main (int argCount, char *argVar[]) {
        int i;
        uchar utf8[4];
        int len = argCount - 1;
    
        if (len != 4) {
                printf ("Usage: utf8 <hex1> <hex2> <hex3> <hex4>\n");
                return 1;
        }
        printf ("Input:      (%d) %s %s %s %s\n",
            len, argVar[1], argVar[2], argVar[3], argVar[4]);
    
        for (i = 0; i < 4; i++)
                utf8[i] = htoc (argVar[i+1]);
    
        printf ("   Becomes: (%d) %02x %02x %02x %02x\n",
            len, utf8[0], utf8[1], utf8[2], utf8[3]);
    
        if ((i = getUtf8 (&(utf8[0]), &len)) < 0)
            printf ("Error %d\n", i);
        else
            printf ("   Finally: U+%x, with length of %d\n", i, len);
    
        return 0;
    }
    

    本质上,如果它以0开头,它就是一个7位代码点。如果它以10开头,则是多字节码点的延续。否则,1的数量告诉您此代码点编码为多少字节

    第一个字节表示编码代码点的字节数

    0xxxxxxx编码为1字节的7位代码点

    110xxxxx 10xxxxxx编码为2字节的10位代码点

    110xxxxx 10xxxxxx 10xxxxxx等。 1110xxxx 11110xxx
    等等。

    如果编码为UTF-8,则下表显示了如何将Unicode码点(最多21位)转换为UTF-8编码:

    Scalar Value                 1st Byte  2nd Byte  3rd Byte  4th Byte
    00000000 0xxxxxxx            0xxxxxxx
    00000yyy yyxxxxxx            110yyyyy  10xxxxxx
    zzzzyyyy yyxxxxxx            1110zzzz  10yyyyyy  10xxxxxx
    000uuuuu zzzzyyyy  yyxxxxxx  11110uuu  10uuzzzz  10yyyyyy  10xxxxxx
    
    有许多不允许的值—特别是字节0xC1、0xC2和0xF5—0xFF永远不会出现在格式良好的UTF-8中。还有许多其他verboten组合。不规则情况出现在第1字节和第2字节列中。请注意,代码U+D800-U+DFFF是为UTF-16代理保留的,不能出现在有效的UTF-8中

    Code Points          1st Byte  2nd Byte  3rd Byte  4th Byte
    U+0000..U+007F       00..7F
    U+0080..U+07FF       C2..DF    80..BF
    U+0800..U+0FFF       E0        A0..BF    80..BF
    U+1000..U+CFFF       E1..EC    80..BF    80..BF
    U+D000..U+D7FF       ED        80..9F    80..BF
    U+E000..U+FFFF       EE..EF    80..BF    80..BF
    U+10000..U+3FFFF     F0        90..BF    80..BF    80..BF
    U+40000..U+FFFFF     F1..F3    80..BF    80..BF    80..BF
    U+100000..U+10FFFF   F4        80..8F    80..BF    80..BF
    
    这些表格从标准版本5.1中删除


    在问题中,来自偏移量0x0010的物料。。0x008F产量:

    0x61           = U+0061
    0x61           = U+0061
    0x61           = U+0061
    0xE6 0xBE 0xB3 = U+6FB3
    0xE5 0xA4 0xA7 = U+5927
    0xE5 0x88 0xA9 = U+5229
    0xE4 0xBA 0x9A = U+4E9A
    0xE4 0xB8 0xAD = U+4E2D
    0xE6 0x96 0x87 = U+6587
    0xE8 0xAE 0xBA = U+8BBA
    0xE5 0x9D 0x9B = U+575B
    0x2C           = U+002C
    0xE6 0xBE 0xB3 = U+6FB3
    0xE6 0xB4 0xB2 = U+6D32
    0xE8 0xAE 0xBA = U+8BBA
    0xE5 0x9D 0x9B = U+575B
    0x2C           = U+002C
    0xE6 0xBE 0xB3 = U+6FB3
    0xE6 0xB4 0xB2 = U+6D32
    0xE6 0x96 0xB0 = U+65B0
    0xE9 0x97 0xBB = U+95FB
    0x2C           = U+002C
    0xE6 0xBE 0xB3 = U+6FB3
    0xE6 0xB4 0xB2 = U+6D32
    0xE4 0xB8 0xAD = U+4E2D
    0xE6 0x96 0x87 = U+6587
    0xE7 0xBD 0x91 = U+7F51
    0xE7 0xAB 0x99 = U+7AD9
    0x2C           = U+002C
    0xE6 0xBE 0xB3 = U+6FB3
    0xE5 0xA4 0xA7 = U+5927
    0xE5 0x88 0xA9 = U+5229
    0xE4 0xBA 0x9A = U+4E9A
    0xE6 0x9C 0x80 = U+6700
    0xE5 0xA4 0xA7 = U+5927
    0xE7 0x9A 0x84 = U+7684
    0xE5 0x8D 0x8E = U+534E
    0x2D           = U+002D
    0x29           = U+0029
    0xE5 0xA5 0xA5 = U+5965
    0xE5 0xB0 0xBA = U+5C3A
    0xE7 0xBD 0x91 = U+7F51
    0x26           = U+0026
    0x6C           = U+006C
    0x74           = U+0074
    0x3B           = U+003B
    

    这方面的一个很好的参考是Markus Kuhn的。

    3字节

    UTF-8的构造方式确保字符的起始位置和字节数不存在可能的歧义

    这真的很简单

    • 0x80到0xBF范围内的字节永远不是字符的第一个字节。
    • 任何其他字节始终是字符的第一个字节。
    UTF-8有很多冗余

    如果你想知道一个字符有多少字节长,有多种方法可以告诉你

    • 第一个字节总是告诉您字符的长度:
      • 如果第一个字节为0x00到0x7F,则为一个字节
      • 0xC2到0xDF表示它是两个字节
      • 0xE0到0x
        0x61           = U+0061
        0x61           = U+0061
        0x61           = U+0061
        0xE6 0xBE 0xB3 = U+6FB3
        0xE5 0xA4 0xA7 = U+5927
        0xE5 0x88 0xA9 = U+5229
        0xE4 0xBA 0x9A = U+4E9A
        0xE4 0xB8 0xAD = U+4E2D
        0xE6 0x96 0x87 = U+6587
        0xE8 0xAE 0xBA = U+8BBA
        0xE5 0x9D 0x9B = U+575B
        0x2C           = U+002C
        0xE6 0xBE 0xB3 = U+6FB3
        0xE6 0xB4 0xB2 = U+6D32
        0xE8 0xAE 0xBA = U+8BBA
        0xE5 0x9D 0x9B = U+575B
        0x2C           = U+002C
        0xE6 0xBE 0xB3 = U+6FB3
        0xE6 0xB4 0xB2 = U+6D32
        0xE6 0x96 0xB0 = U+65B0
        0xE9 0x97 0xBB = U+95FB
        0x2C           = U+002C
        0xE6 0xBE 0xB3 = U+6FB3
        0xE6 0xB4 0xB2 = U+6D32
        0xE4 0xB8 0xAD = U+4E2D
        0xE6 0x96 0x87 = U+6587
        0xE7 0xBD 0x91 = U+7F51
        0xE7 0xAB 0x99 = U+7AD9
        0x2C           = U+002C
        0xE6 0xBE 0xB3 = U+6FB3
        0xE5 0xA4 0xA7 = U+5927
        0xE5 0x88 0xA9 = U+5229
        0xE4 0xBA 0x9A = U+4E9A
        0xE6 0x9C 0x80 = U+6700
        0xE5 0xA4 0xA7 = U+5927
        0xE7 0x9A 0x84 = U+7684
        0xE5 0x8D 0x8E = U+534E
        0x2D           = U+002D
        0x29           = U+0029
        0xE5 0xA5 0xA5 = U+5965
        0xE5 0xB0 0xBA = U+5C3A
        0xE7 0xBD 0x91 = U+7F51
        0x26           = U+0026
        0x6C           = U+006C
        0x74           = U+0074
        0x3B           = U+003B
        
        function get_length(field_selector) {
          var escapedStr = encodeURI($(field_selector).val())
          if (escapedStr.indexOf("%") != -1) {
            var count = escapedStr.split("%").length - 1
            if (count == 0) count++  //perverse case; can't happen with real UTF-8
            var tmp = escapedStr.length - (count * 3)
            count = count + tmp
          } else {
            count = escapedStr.length
          }
          return count
        }