How does a file with Chinese characters know how many bytes to use per character?
我读过乔尔的文章"每个软件开发人员绝对,绝对,绝对必须了解Unicode和字符集(没有借口!)"但仍然不了解所有细节。一个例子将说明我的问题。查看以下文件:
。(source:yart.com.au)
我在二进制编辑器中打开了文件,仔细检查了第一个汉字旁边的三个a中的最后一个:
氧化镁(source:yart.com.au)
乔尔说:
In UTF-8, every code point from 0-127 is stored in a single byte. Only code points 128 and above are stored using 2, 3, in fact, up to 6 bytes.
号
编辑也这么说:
百万千克1E6(230)高于代码点128。百万千克1百万千克1因此,我将把下面的字节解释为2,3,实际上,最多6个字节。百万千克1
如果是这样,说明解释超过2个字节的是什么?E6后面的字节是如何指示这一点的?
我的汉字是以2、3、4、5或6字节存储的吗?
如果编码为UTF-8,下表显示了如何将Unicode码位(最多21位)转换为UTF-8编码:
1 2 3 4 5 | 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中。还有许多其他的原话组合。第一个字节和第二个字节列中存在不规则。请注意,代码U+D800-U+DFFF是为UTF-16代理保留的,不能以有效的UTF-8显示。
1 2 3 4 5 6 7 8 9 10 | 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 |
这些表是从Unicode标准版本5.1中提取的。
在问题中,来自偏移量0x0010的材料..0x00 8F产量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | 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 |
这都是UTF8编码的一部分(对于Unicode来说,这只是一种编码方案)。
通过检查第一个字节可以计算出大小,如下所示:
- 如果它以位模式
"10" (0x80-0xbf) 开头,它不是序列的第一个字节,您应该备份到找到开始,任何以"0"或"11"开头的字节(感谢Jeffrey Hantin在注释中指出)。 - 如果它以位模式
"0" (0x00-0x7f) 开头,则为1字节。 - 如果它以位模式
"110" (0xc0-0xdf) 开头,则为2字节。 - 如果它以位模式
"1110" (0xe0-0xef) 开头,则为3字节。 - 如果它以位模式
"11110" (0xf0-0xf7) 开头,则为4字节。
我会复制显示这个的表格,但原件在维基百科的utf8页面上。
1 2 3 4 5 6 7 8 | +----------------+----------+----------+----------+----------+ | Unicode | Byte 1 | Byte 2 | Byte 3 | Byte 4 | +----------------+----------+----------+----------+----------+ | U+0000-007F | 0xxxxxxx | | | | | U+0080-07FF | 110yyyxx | 10xxxxxx | | | | U+0800-FFFF | 1110yyyy | 10yyyyxx | 10xxxxxx | | | U+10000-10FFFF | 11110zzz | 10zzyyyy | 10yyyyxx | 10xxxxxx | +----------------+----------+----------+----------+----------+ |
上表中的Unicode字符由以下位构成:
1 | 000z-zzzz yyyy-yyyy xxxx-xxxx |
如果没有给定,则假定
- 无用:以0xc0或0xc1开头的2字节序列实际给出的代码点小于0x80,这可以用1字节序列更好地表示。
- 由rfc3629用于U+10ffff以上的4字节序列,或5字节和6字节序列。这些是字节0xf5到0xfd。
- 只是未使用:字节0xfe和0xff。
此外,多字节序列中不以位"10"开头的后续字节也是非法的。
例如,考虑序列[0xf4,0x8a,0xaf,0x8d]。这是一个4字节序列,因为第一个字节介于0xf0和0xf7之间。
1 2 3 4 5 6 7 8 | 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)的特定查询,字节序列为:
1 2 3 4 5 6 7 8 | 0xe6 0xbe 0xb3 = 11100110 10111110 10110011 yyyy yyyyxx xxxxxx = 01101111 10110011 yyyyyyyy xxxxxxxx = U+6FB3 |
如果你在这里查到代码,你会发现它就是你问题中的代码:澳大利亚。
为了展示解码是如何工作的,我回到我的档案中找到了我的utf8处理代码。为了使它成为一个完整的程序,我必须对它进行一些变形,并且编码已经被删除(因为问题实际上是关于解码的),所以我希望我没有从剪切粘贴中引入任何错误:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | #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> "); return 1; } printf ("Input: (%d) %s %s %s %s ", 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 ", len, utf8[0], utf8[1], utf8[2], utf8[3]); if ((i = getUtf8 (&(utf8[0]), &len)) < 0) printf ("Error %d ", i); else printf (" Finally: U+%x, with length of %d ", i, len); return 0; } |
您可以使用字节序列运行它(您需要4个字节,因此使用0填充它们),如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | > 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 |
一个很好的参考是markus kuhn的utf-8和unicode faq。
基本上,如果它以0开头,它是一个7位的代码点。如果以10开头,则是多字节码位的延续。否则,1的数字告诉您这个代码点编码为多少字节。
第一个字节表示编码码位的字节数。
0xXXXXXX以1字节编码的7位码位
110xxxxx 10xxxx 10位码位,以2字节编码
110XXXXX 10XXXXXX 10XXXXXX等1110XXXX111xxxx等。
UTF-8的构造方式使得字符的起始位置和字节数没有可能的模糊性。
这真的很简单。
- 范围从0x80到0xBF的字节绝不是字符的第一个字节。
- 任何其他字节总是字符的第一个字节。
UTF-8有很多冗余。
如果您想知道一个字符有多少字节长,有多种方法可以知道。
- 第一个字节总是告诉您字符的长度:
- 如果第一个字节是0x00到0x7F,则为一个字节。
- 0xc2到0xdf表示它是两个字节。
- 0xe0到0xef表示它是三个字节。
- 0xf0到0xf4意味着它是四个字节。
- 或者,您可以只计算0x80到0xBF范围内的连续字节数,因为这些字节都属于与前一个字节相同的字符。
有些字节从未被使用过,比如0xc1到0xc2或0xf5到0xff,所以如果在任何地方遇到这些字节,那么就不会看到utf-8。
3字节http://en.wikipedia.org/wiki/utf-8说明
最多0x7ff的代码点存储为2个字节;最多0xffff存储为3个字节;其他所有代码点存储为4个字节。(从技术上讲,最多为0x1ffff,但Unicode中允许的最高代码点是0x10ffff。)
解码时,多字节序列的第一个字节用于确定用于生成序列的字节数:
序列中的所有后续字节必须符合
提示在下面的句子中:
In UTF-8, every code point from 0-127
is stored in a single byte. Only code
points 128 and above are stored using
2, 3, in fact, up to 6 bytes.
最高127的每个代码点的顶位都设置为零。因此,编辑器知道,如果遇到顶部位为1的字节,它就是多字节字符的开头。
为什么有这么多复杂的答案?
一个汉字3个字节。使用此函数(在jquery下):
1 2 3 4 5 6 7 8 9 10 11 12 | 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 } |