如何对UTF-8字符串数组进行排序?

时间:2020-03-06 14:35:31  来源:igfitidea点击:

我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中使用可识别语言环境的字符串操作。