如何对UTF-8字符串数组进行排序?
我currentyl不知道如何对包含PHP中UTF-8编码字符串的数组进行排序。该阵列来自LDAP服务器,因此通过数据库排序(不会有问题)不是解决方案。
以下内容不适用于我的Windows开发计算机(尽管我认为这至少应该是一个可能的解决方案):
$array=array('Birnen', '?pfel', 'Ungetüme', 'Apfel', 'Ungetiere', '?sterreich'); $oldLocal=setlocale(LC_COLLATE, "0"); var_dump(setlocale(LC_COLLATE, 'German_Germany.65001')); usort($array, 'strcoll'); var_dump(setlocale(LC_COLLATE, $oldLocal)); var_dump($array);
输出为:
string(20) "German_Germany.65001" string(1) "C" array(6) { [0]=> string(6) "Birnen" [1]=> string(9) "Ungetiere" [2]=> string(6) "?pfel" [3]=> string(5) "Apfel" [4]=> string(9) "Ungetüme" [5]=> string(11) "?sterreich" }
这是完全废话。使用1252作为setlocale()
的代码页会给出另一种输出,但仍然是一个明显错误的输出:
string(19) "German_Germany.1252" string(1) "C" array(6) { [0]=> string(11) "?sterreich" [1]=> string(6) "?pfel" [2]=> string(5) "Apfel" [3]=> string(6) "Birnen" [4]=> string(9) "Ungetüme" [5]=> string(9) "Ungetiere" }
有没有一种方法可以对带有UTF-8字符串语言环境的数组进行排序?
刚刚指出,这似乎是Windows上的PHP问题,因为与de_DE.utf8
作为语言环境的相同代码段在Linux机器上工作。不过,针对此Windows特定问题的解决方案将是不错的...
解决方案
这是一个非常复杂的问题,因为UTF-8编码的数据可以包含任何Unicode字符(即,来自许多8位编码的字符,它们在不同的语言环境中进行不同的排序)。
也许,如果我们将UTF-8数据转换为Unicode(不熟悉PHP unicode函数,对不起),然后将其标准化为NFD或者NFKD,然后对代码点进行排序,可能会得出一些对我们有意义的排序规则(即" A"前 "?")。
检查我提供的链接。
编辑:由于我们提到输入数据是明确的(我假设它们都属于" windows-1252"代码页),那么我们应该执行以下转换:UTF-8 Unicode Windows-1252,Windows-1252对其进行编码选择" CP1252"语言环境进行排序。
在Windows开发机上,将示例与代码页1252一起使用非常好。
$array=array('Birnen', '?pfel', 'Ungetüme', 'Apfel', 'Ungetiere', '?sterreich'); $oldLocal=setlocale(LC_COLLATE, "0"); var_dump(setlocale(LC_COLLATE, 'German_Germany.1252')); usort($array, 'strcoll'); var_dump(setlocale(LC_COLLATE, $oldLocal)); var_dump($array);
...剪...
这是PHP 5.2.6. 顺便提一句。
上面的示例是错误的,它使用ASCII编码而不是UTF-8. 我确实跟踪了strcoll()调用并查看了发现的内容:
function traceStrColl($a, $b) { $outValue = strcoll($a, $b); echo "$a $b $outValue\r\n"; return $outValue; } $array=array('Birnen', '?pfel', 'Ungetüme', 'Apfel', 'Ungetiere', '?sterreich'); setlocale(LC_COLLATE, 'German_Germany.65001'); usort($array, 'traceStrColl'); print_r($array);
给出:
Ungetüme ?pfel 2147483647 Ungetüme Birnen 2147483647 Ungetüme Apfel 2147483647 Ungetüme Ungetiere 2147483647 ?sterreich Ungetüme 2147483647 ?pfel Ungetiere 2147483647 ?pfel Birnen 2147483647 Apfel ?pfel 2147483647 Ungetiere Birnen 2147483647
我确实发现了一些错误报告,这些错误报告被标记为虚假...
我最好的选择是提交一个错误报告,不过我想...
排序规则需要与字符集匹配。由于数据是UTF-8编码的,因此我们应该使用UTF-8归类。在不同的平台上,它的名称可能不同,但是一个很好的猜测是de_DE.utf8
。
在UNIX系统上,我们可以使用以下命令获取当前安装的语言环境的列表:
locale -a
最终,由于Huppie发现了一个明显的PHP错误,因此如果不使用建议的重新编码字符串(UTF-8 Windows-1252或者ISO-8859-1),就无法以简单的方式解决此问题。
总结问题,我创建了以下代码片段,清楚地说明了问题是使用65001 Windows-UTF-8代码页时的strcoll()函数。
function traceStrColl($a, $b) { $outValue=strcoll($a, $b); echo "$a $b $outValue\r\n"; return $outValue; } $locale=(defined('PHP_OS') && stristr(PHP_OS, 'win')) ? 'German_Germany.65001' : 'de_DE.utf8'; $string="ABCDEFGHIJKLMNOPQRSTUVWXYZ??üabcdefghijklmnopqrstuvwxyz??ü?"; $array=array(); for ($i=0; $i<mb_strlen($string, 'UTF-8'); $i++) { $array[]=mb_substr($string, $i, 1, 'UTF-8'); } $oldLocale=setlocale(LC_COLLATE, "0"); var_dump(setlocale(LC_COLLATE, $locale)); usort($array, 'traceStrColl'); setlocale(LC_COLLATE, $oldLocale); var_dump($array);
结果是:
string(20) "German_Germany.65001" a B 2147483647 [...] array(59) { [0]=> string(1) "c" [1]=> string(1) "B" [2]=> string(1) "s" [3]=> string(1) "C" [4]=> string(1) "k" [5]=> string(1) "D" [6]=> string(2) "?" [7]=> string(1) "E" [8]=> string(1) "g" [...]
相同的代码片段在Linux机器上可以正常工作,而不会产生以下输出问题:
string(10) "de_DE.utf8" a B -1 [...] array(59) { [0]=> string(1) "a" [1]=> string(1) "A" [2]=> string(2) "?" [3]=> string(2) "?" [4]=> string(1) "b" [5]=> string(1) "B" [6]=> string(1) "c" [7]=> string(1) "C" [...]
当使用Windows-1252(ISO-8859-1)编码的字符串(当然mb_ *编码和语言环境必须更改)时,该代码段也可以使用。
我在bugs.php.net上提交了一个错误报告:错误#46165 strcoll()在Windows上不适用于UTF-8字符串。如果我们遇到相同的问题,可以在错误报告页面上向PHP团队提供反馈(另外两个可能是相关的错误被归类为伪造,我认为此错误不是伪造的;-)。
感谢大家。
有关此问题的更新:
即使围绕该问题的讨论表明我们可以通过strcoll()
和/或者setlocale()
发现一个PHP错误,但事实并非如此。问题是Windows CRT实现setlocale()的局限性(PHPs setlocale()只是围绕CRT调用的薄包装)。以下是对MSDN页面" setlocale,_wsetlocale"的引用:
The set of available languages, country/region codes, and code pages includes all those supported by the Win32 NLS API except code pages that require more than two bytes per character, such as UTF-7 and UTF-8. If you provide a code page like UTF-7 or UTF-8, setlocale will fail, returning NULL. The set of language and country/region codes supported by setlocale is listed in Language and Country/Region Strings.
因此,如果字符串是多字节编码的,则无法在Windows上的PHP中使用可识别语言环境的字符串操作。