在UTF-8中,所有 ASCII 字符127
都由一个字节(的二进制表示0xxxxxxx
)表示,大于的代码点127
由多字节序列表示。多字节序列由一个前导字节和一个或多个连续字节组成。
前导字节的高位用于告诉我们要使用多少个连续字节,为此它有两个或多个高位 1,后跟一个 0,即高位可以是110
or1110
或11110
or 111110
。高位位数等于前导字节加上后续字节的总和,即
110 means 1 leading byte + 1 continuation byte
1110 means 1 leading byte + 2 continuation bytes
11110 means 1 leading byte + 3 continuation bytes
跟在前导字节后面的连续字节的格式为10xxxxxx
.
将上述内容应用于您的$test
字符串:
我们有 3 个字节ord('X')
,它们都是ascii字符127
,因此它们被计为 1 个字符到 1 个字节,
然后我们有一个chr(241)
11110001 的二进制表示,所以它是一个前导字节,因为它有两个或多个高位。
由于它有 4 个高位,这意味着它表示的代码点由 1 个前导字节加上 3 个连续字节组成,因此ord('X')
字符串中剩余的 3 个字节被mb_strlen()
视为连续字节*,尽管与 chr(241) 一起是总共四个字节,它们被视为一个 UTF-8 代码点。
*这里我们必须声明那些尾随的'X'不是有效的连续字节,因为它们不符合连续字节的标准。但是mb_strlen()
,如上所述,在chr(241)
. 如果您添加另一个' 或从字符串末尾'X
减去,您可以对此进行测试。'X's
$test
更新:验证结果:
/*
* The following strings are non valid UTF-8 encodings.
* We test to see if mb_strlen() consumes non VALID UTF-8
* byte strings like they are valid (driven by the leading bytes)
*
*/
/*
* 0xc0 as a leading byte should consume one continuation byte
* so the length reported should be 6
*/
$test = 'XXX' . chr(0xc0) . 'XXX';
echo '6 == ', mb_strlen($test, 'UTF8');
/*
* 0xe0 as a leading byte should consume two continuation bytes
* so the length reported should be 5
*/
$test = 'XXX' . chr(0xe0) . 'XXX';
echo '5 == ', mb_strlen($test, 'UTF8'), PHP_EOL;
// results in 6 == 6 and 5 == 5
更新 2:
chr()
在 Latin-1 和 UTF-8 中使用相同符号构造的示例。
$euroSignAscii = chr(0x80); // Latin-1 extended ASCII
$euroSignUtf8 = chr(0xe2) . chr(0x82) . chr(0xac); // UTF-8
请注意,如果您将上述字符串回显到控制台或网页的编码(如果是 latin-1,$euroSignAscii
则将正确输出,如果是 UTF-8,$euroSignUtf8
则将正确输出)。
链接:
一个很好的参考是维基百科上的相关 UTF-8 文章
Joel Spolsky的经典帖子 每个软件开发人员绝对、绝对必须了解 Unicode 和字符集的绝对最低要求(没有借口!)
并去感受一下UTF-8 编码表和 Unicode 字符