昨天工作中遇到了一個問題問題: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