我读过Joel的文章"The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)",但仍然不了解所有细节。一个例子将说明我的问题。请看下面这个文件:
(来源:yart.com.au)
我在二进制编辑器中打开文件,仔细检查第一个汉字旁边的三个a中的最后一个:
(来源:yart.com.au)
据乔尔说:
在UTF-8中,0-127的每个代码点都存储在一个字节中。只使用2,3存储代码点128及以上,实际上最多6个字节。
编辑说:
如果是,那表示解释超过2个字节?这是如何用E6后面的字节表示的?
我的汉字是以2,3,4,5或6字节存储的吗?
答案 0 :(得分:28)
如果编码为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
这些表格取自Unicode标准版本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
答案 1 :(得分:22)
这是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个字节。我将复制显示此内容的表格,但原文位于Wikipedia UTF8页面here上。
+----------------+----------+----------+----------+----------+
| 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字符由位构成:
000z-zzzz yyyy-yyyy xxxx-xxxx
其中假定z
和y
位为零,而未给出它们。一些字节被认为是非法的起始字节,因为它们是:
此外,多字节序列中不以“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
如果您查看代码here,您会看到它就是您在问题中所拥有的代码:澳。
为了说明解码是如何工作的,我回到我的档案中找到了我的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;
}
您可以使用您的字节序列运行它(您需要4,因此请使用0来填充它们),如下所示:
> 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
答案 2 :(得分:5)
对此的一个很好的参考是Markus Kuhn的UTF-8 and Unicode FAQ。
答案 3 :(得分:3)
基本上,如果它以0开头,那么它是一个7位代码点。如果它以10开头,则它是多字节码点的延续。否则,1的数量会告诉您此代码点被编码为的字节数。
第一个字节表示编码代码点的字节数。
0xxxxxxx 7位代码点以1字节编码
110xxxxx 10xxxxxx以2个字节编码的10位代码点
110xxxxx 10xxxxxx 10xxxxxx等 1110xxxx 11110xxx 等
答案 4 :(得分:2)
最多0x7ff的代码点存储为2个字节;最多0xffff为3个字节;其他一切都是4个字节。 (从技术上讲,最高为0x1fffff,但Unicode中允许的最高代码点为0x10ffff。)
解码时,多字节序列的第一个字节用于确定用于生成序列的字节数:
110x xxxx
=&gt; 2字节序列1110 xxxx
=&gt; 3字节序列1111 0xxx
=&gt; 4字节序列序列中的所有后续字节必须符合10xx xxxx
模式。
答案 5 :(得分:2)
答案 6 :(得分:2)
UTF-8的构造方式使得字符的起始位置和字节数不存在歧义。
这很简单。
UTF-8有很多冗余。
如果你想告诉一个字符有多长字节,有多种方法可以告诉你。
从不使用某些字节,例如0xC1到0xC2或0xF5到0xFF,所以如果你在任何地方遇到这些字节,那么你不会看UTF-8。
答案 7 :(得分:1)
提示在这句话中:
在UTF-8中,每个代码都指向0-127 存储在一个字节中。只有代码 使用存储点128及以上 2,3,实际上最多6个字节。
每个最多127个代码点的最高位都设置为零。因此,编辑器知道如果遇到顶部位为1的字节,则它是多字节字符的开头。
答案 8 :(得分:0)
为什么会有这么多复杂的答案?
1个汉字3个字节。使用此函数(在jQuery下):
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
}