0

所以我遇到了这个问题,我尽可能地简化了它。

$test = 'XXX' . chr(241) . 'XXX';
print($test); // XXX�XXX
print(mb_strlen($test, 'UTF-8')); // 4
print(count(str_split($test))); // 7

所以基本上我的问题是:为什么 chr(241) 不返回一个使字符串长度为 7 的字符?是六个字,我加一个,是四个字?为什么 chr(241) 不等于 html 实体 241?

下面列出的其他信息。请注意,只要您不在 chr(241) 之后添加 X,每个人都很高兴:

print(mb_detect_encoding($test)); // UTF-8
print(mb_strlen('XX' . chr(241) . 'XX', 'UTF-8')); // 3
print(mb_strlen('X' . chr(241) . 'X', 'UTF-8')); // 2
print(mb_strlen('' . chr(241) . 'X', 'UTF-8')); // 1
print(mb_strlen('X' . chr(241) . '', 'UTF-8')); // 2
print(mb_strlen('XXX' . chr(241) . '', 'UTF-8')); // 4
print(mb_strlen(chr(241), 'UTF-8')); // 1

这似乎是一个编码问题,但如何?该文件保存为 UTF-8,内部编码是 UTF-8,我不会在任何地方传递数据来搞砸它。

4

1 回答 1

3

UTF-8中,所有 ASCII 字符127都由一个字节(的二进制表示0xxxxxxx)表示,大于的代码点127多字节序列表示。多字节序列由一个前导字节和一个或多个连续字节组成

前导字节的高位用于告诉我们要使用多少个连续字节,为此它有两个或多个高位 1,后跟一个 0,即高位可以是110or111011110or 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 字符

于 2013-10-25T17:45:29.623 回答