字符串以及为什么说 PHP 不支持 Unicode

一、编码

        首先明白一点,我们平时说的编码其实包括两个概念:支持的字符集、编码方案。

        支持的字符集表示在某种编码下的字符所对应的码值。

        编码方案表示在某种编码下的字符在内部的存储实现。

        以 ASCII 为例,它支持的字符集是 0-127在内的每个码值对应一个字符,编码方案是每个字符以一个字节存储。以 Unicode 的 UTF-8 为例,它支持的字符集是用多个十六进制表示的码值,每个码值对应一个字符,编码方案是以多个连续字节表示一个字符,如中文以 3 个连续字节表示一个字面字符。

二、PHP 字符串

        概念:一个字符串就是由一系列的字符组成,其中每个字符等同于一个字节。

        即相当于字符串存只能支持单字节能表示的 0 - 255 在内的字符集,而 Unicode 支持的码值可能会是如 \x0bec6a 这样的码值,当然不在 0 - 255 之内,甚至它的最小码值都远远大于这个范围,所以说,PHP 不支持 Unicode,其实是说 PHP 不支持 Unicode 的字符集。

        ①字符串详解

        PHP 中的字符串的实现方式是一个由字节组成数组再加上一个整数指明缓冲区长度。即字符串在内部其实是由一个数组构成,其中的每个元素是一个字节,可能为二进制代码或其他。

        并无如何将字节转换成字符的信息。即字符串在内部的每个字节仅仅表示一个字节的位表示,它并不表示什么字面的字符意思,即 PHP 没有指明任何的编码。

        内部实现以 PHP 的方式举例,字符串在内部可能表示如下:

$str1 = 'abc';
$str2 = '中文';

//假设文件编码为 UTF-8,下列的二进制为伪造,仅仅为了演示
$str1 = [01010101, 01101101, 01110111];
$str2 = [00000001, 00000010, 00000011, 00000100, 00000101, 00000110];

        可以看到,数组中的每个元素仅仅是一个字节的二进制位表示。

        既然 PHP 没有指明任何的编码,那代码到底是以怎样的方式被转换上述格式,又是以怎样的方式从上述格式转换为字面字符的,也就是说 PHP 字符串到底是如何被编码和解码的?

        答案是:PHP 会根据文件的编码对代码进行编码和解码。即假设文件编码为 UTF-8,我们知道汉字以 3 个字节存储,能表示一个汉字字符,那么 PHP 解析器对字符串的编码即内部实现就是存储 3 个连续的字节,在解码时取 3 个连续的字节作为一个组合再根据文件的编码去找到对应的字面字符。

        由于我暂时没去看字符串处理的源码实现,所以大致猜一下,比如仍然是 UTF-8,混合字符的实现如 “a中文b”,严格的按照 Unicode 的编码方案的话 “a” 和 “b” 其实是存储的 2 个字节,前面字节为补 0,但是在 PHP 中,在编码时可能会对每个字节和 ASCII 码表对比,如果是单字节字符就以 1 个字节存储,如果对比不符则表示多字节,则根据文件编码的编码方案存储连续的多个字节,同理解码时可能也类似。

        然而,PHP 的绝大部分内部字符串处理函数其实都是针对单字节的,比如常见的 strlen(),substr(),strpos(),strcmp()......如果应用这些函数对多字节字符处理,可能会得到预想不到的结果,比如上述代码中 strlen($str2),得到的是 6。所以,在处理多字节字符串时,需要考虑到这点,需要 mbstring 扩展的支持,该扩展默认已经安装了,可能需要开启。

        ②关于双引号下有特殊意义的字符组合

字符组合含义
\n换行(ASCII 字符集中 LF 或 0x0A(10))
\r回车(ASCII 字符集中 CR 或 0x0D (13))
\t水平制表符(ASCII 字符集中 HT 或 0x09(9))
\v垂直制表符(ASCII 字符集中 VT 或 0x0B(11))
\eEscape(ASCII 字符集中 ESC 或 0x1B(27))
\f换页(ASCII 字符集中 FF 或 0x0C(12))
\\反斜线
\$单纯的美元符号
\"双引号
\[0-7]{1,3}是一个八进制数字表示的字符
\x[0-9a-fA-F]{1,2}是一个十六进制数字表示的字符

        ③变量解析规则

        定义:当 PHP 解析器遇到一个 “$” 符号时,会组合尽量多的标识符以形成一个合法的变量名。

$str = 'hello';
echo "$strworld";

$arr = ['one', 'two'];
echo "$arr[0]two";

        上述的代码都不会有输出,并且会报 Notice 通知,未定义的变量。其实是因为 PHP 解析器在解析变量时,会把美元符号后续的字符尽可能多的组合,只要是一个合法的变量名,都当做变量处理,上述情景一实际解析的变量名为 “$strworld”,同理二也类似,自然就找不到变量。

        这种情况在变量写在双引号字符串内是需要注意,一般可能会把变量写在字符串外部以 “.” 连接;如果要写在双引号内,则必须制定其变量边界,使用大括号语法,注意,左边的大括号必须与美元符仅仅相连,之间不能有其他字符包括空白字符。

$str = 'hello';
echo "{$str}world" . PHP_EOL;
echo "${str}world" . PHP_EOL;

$arr = ['one', 'two'];
echo "{$arr[0]}two" . PHP_EOL;
echo "${arr[0]}two";


//以下 $ 与左边的大括号之间有其他字符,不会被当做变量解析,被当做单纯的字符串输出
echo "{ $str}";
echo "$ {arr[0]}";

        注意,在双引号内使用带引号键名的数组时,必须使用上述语法,否则无法解析。

        ④存储、修改字符串

        由上述的字符串详解得出,字符串其实可以使用数组的形式来访问,即每个字符以下标从 0 开始计数,注意如果是多字节就不要这样操作了,从内部实现得知,以下标访问多字节肯定会乱码。

        关于以下标的形式修改字符串,当修改的字符串下标大于原字符串长度时,会拉升字符串,未定义的部分以空格填充;当以一个字符串修改原字符串的某个字符时,只是将字符串的第一个字符串替换原字符串的对应位置字符。

$str = 'abc';
echo strlen($str) . PHP_EOL;

$str[4] = 'd';
echo strlen($str) . PHP_EOL;    //5
echo $str . PHP_EOL;                      //'abc d'

$replace = 'hello';
$str[3] = $replace;
echo $str;                      //'abchd',只会将替换字符串的第一个字符替换

 

©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页