昨天工作中遇到了一个问题问题:php程序从数据库中取出变量$a(值为"car ws"),经过iconv转码从utf8到gb2312后,赋值给$b,结果判断语句$a == $b 竟然是false!
究其原因原来是这里的空格是不换行空格。
什么是不换行空格?
不换行空格(non-blocking space)也是一种空格,它和普通空格的区别在于在排版的时候表现不同。比如在word中,下面一段话在“如果它”三个字中间有两个空格,第一段是使用普通空格的排版表现,第二段是使用不换行空格(word中输入不换行空格使用ctrl+shift+space)的排版表现。
第一段中“如”和“果”字中间的空格就被换行了,而第二段中的“如果它”三个字中的不换行空格就像是告诉word:这三个字是连在一起的,你如果要进行换行,就把我们一起进行换行,不能从中间隔断~所以谓之“不换行空格”。
在html中也有不换行空格的概念, (本身就是non-blocking space的缩写)。在html中使用 也能达到不换行的目的,比如下面这个例子:
文字中的“排版时”三个字中间有空格,第一段使用普通空格,第二段使用 (html字符集中的non-blocking),第三段使用UTF-8字符集中的non-blocking space(我是直接从word中复制出来的,sublime text中如何输入non-blocking space没查出来,有知道的麻烦告诉一下)
在浏览器下显示情况:
之所以这么表现的原因和word中是一样的。
在HTML中,页面会合并多个连续的空格,而不换行空格是禁止合并的,因此它也称为“硬空格”,“固定空格”。
不同编码中的不换行空格
在不同编码下的不换行空格有不同的值设定,比如
在UTF8中,值为0xC2,0xA0(所以使用urlencode编码不换行空格会显示%c2%a0)
在HTML中,值为 ,或者 (可以参考HTML特殊字符对照表http://www.jb51.net/onlineread/htmlchar.htm)
下图是不换行空格在不同字符集的表示
编码转换中的损失
从上张表中可以看出,最大的问题就是不换行空格并不是在所有编码中都存在,比如在我们经常使用到的GBK,GB2312,GB18032中都是没有不换行空格的。于是在字符转码中就会遇到问题了,主要归结于从一个有不换行空格的编码转换到没有不换行空格的编码的时候,程序会怎么处理这个不换行空格?
在php中,看下面这个程序:
<?php $normalspace = " "; $nonblockingspace = " "; echo urlencode($normalspace), PHP_EOL; echo urlencode($nonblockingspace), PHP_EOL; $normalspace_gb2312 = iconv("UTF-8", "GB2312", $normalspace); $nonblockingspace_gb2312 = iconv("UTF-8", "GB2312", $nonblockingspace); echo urlencode($normalspace_gb2312), PHP_EOL; echo urlencode($nonblockingspace_gb2312), PHP_EOL; $normalspace_gb2312_2 = iconv("UTF-8", "GB2312//TRANSLIT", $normalspace); $nonblockingspace_gb2312_2 = iconv("UTF-8", "GB2312//TRANSLIT", $nonblockingspace); echo urlencode($normalspace_gb2312_2), PHP_EOL; echo urlencode($nonblockingspace_gb2312_2), PHP_EOL;
(其中的nonblockingspace是不换行空格,在vim中是使用ctrl +k, shift +N, shift +S打出来的)
输出:
在iconv的out_charset不加//IGNORE或者//TRANSLIT的情况下出现的Notice就是提示说这里的$nonblockingspace在GB2312中是没有的,强制转换可能会出现问题。然后在iconv中就把它过滤掉(准确来说是从开头到第一个非法字符截断了),因此$nonblockingspace_gb2312 是空字符串。
而iconv加上了//TRANSLIT之后,php程序会去GB2312字符集中寻找和不换行空格最相近的一个字符(也就是空格),因此$nonblockingspace_gb2312_2是普通空格。
解决问题
到这里最初的问题就完全可以理解了
php程序从数据库中取出变量$a(值为"car ws"),经过iconv转码从utf8到gb2312后,赋值给$b,结果判断语句$a == $b 竟然是false!
首先我的数据库一定是支持不换行空格的字符集(我的数据库是UTF-8编码的)在库中存的变量$a实际上是car[不换行空格]ws。程序取出变量$a后中间就带有了不换行空格,再经过iconv转换后,不管是使用//IGNORE,//TRANSLIT,还是不做任何处理,它们两个都不可能是相等的了。
于是就知道了解决办法,在转码之前先做一次replace操作,否则转码后就是不可逆的了。
$a = str_replace(" ", " ", $a);
于是我和我的小朋友们不用再郁闷了。
参考文章
http://www.jb51.net/onlineread/htmlchar.htm
http://zh.wikipedia.org/wiki/%E4%B8%8D%E6%8D%A2%E8%A1%8C%E7%A9%BA%E6%A0%BC