PHP - 包含 emojis/special 个字符的字符串长度

PHP - length of string containing emojis/special chars

我正在为移动应用程序构建 API,我似乎无法计算包含表情符号的字符串的长度。我的代码:

$str = "✌️ @mention";

printf("strlen: %d" . PHP_EOL, strlen($str));
printf("mb_strlen UTF-8: %d" . PHP_EOL, mb_strlen($str, "UTF-8"));
printf("mb_strlen UTF-16: %d" . PHP_EOL, mb_strlen($str, "UTF-16"));
printf("iconv UTF-16: %d" . PHP_EOL, iconv_strlen(iconv("UTF-8", "UTF-16", $str)));
printf("iconv UTF-16: %d" . PHP_EOL, iconv_strlen(iconv("ISO-8859-1", "UTF-16", $str)));

对此的回应是:

strlen: 27
mb_strlen UTF-8: 14
mb_strlen UTF-16: 13
iconv UTF-16: 14
iconv UTF-16: 27

但是我应该得到 17 作为结果。我们尝试计算 iOS、android 和 windows phone 上的字符串长度,到处都是 17。 iOS (swift) 片段:

var str = "✌️ @mention"
(str as NSString).length // 17
count(str) // 13
count(str.utf16) // 17
count(str.utf8) // 27

由于库的原因,我们需要使用 NSString。我需要这个来获得“@mention”的开始和结束位置。如果字符串仅包含文本或仅包含表情符号,则它可以正常工作,因此混合内容可能存在一些问题。

我做错了什么?我还能向你们提供哪些其他信息,让我朝着正确的方向前进?

谢谢!

你的函数都在计算不同的东西。

Graphemes:                                       ✌                ️                     @       m      e      n      t      i      o      n    13
                      -----------  -----------  --------  ---------------------  ------ ------ ------ ------ ------ ------ ------ ------ ------
Code points:            U+1F44D      U+1F3FF     U+270C     U+1F3FF     U+FE0F   U+0020 U+0040 U+006D U+0065 U+006E U+0074 U+0069 U+006F U+006E  14
UTF-16 code units:     D83D DC4D    D83C DFFF     270C     D83C DFFF     FE0F     0020   0040   006D   0065   006E   0074   0069   006F   006E   17
UTF-16-encoded bytes: 3D D8 4D DC  3C D8 FF DF   0C 27    3C D8 FF DF   0F FE    20 00  40 00  6D 00  65 00  6E 00  74 00  69 00  6F 00  6E 00   34
UTF-8-encoded bytes:  F0 9F 91 8D  F0 9F 8F BF  E2 9C 8C  F0 9F 8F BF  EF B8 8F    20     40     6D     65     6E     74     69     6F     6E    27

PHP 字符串本身是字节。

strlen()统计一个字符串的字节数:27.

mb_strlen(..., 'utf-8') 计算当使用 UTF-8 编码将字符串的字节解码为字符时,字符串中代码点(Unicode 字符)的数量:14.

(其他示例计数基本上没有意义,因为它们基于将输入字符串视为一种编码,而实际上它包含不同编码的数据。)

NSString 本身被计为 UTF-16 代码单元。有 17 个,而不是 14 个,因为上面的字符串包含像 </code> 这样的字符,不适合单个 UTF-16 代码单元,因此必须编码为代理项对。 PHP 中没有任何函数可以对 UTF-16 代码单元中的字符串进行计数,但是由于每个代码单元都被编码为两个字节,因此您可以通过编码为 UTF-16 并将字节数乘以二:</p> <pre><code>strlen(iconv('utf-8', 'utf-16le', $str)) / 2

(注意:le 后缀是使 iconv 编码为 UTF-16 的特定字节顺序所必需的,并且不会通过选择一个并在开头添加 BOM 来弄乱计数说明它选择了哪一个字符串。)

我附上了一张图片来帮助说明@bobince 给出的答案。

本质上,所有 non-surrogate-pair 代码点在 UTF-16 中以两个字节结束,而所有 surrogate-pair 代码点以四个字节结束。如果我们将其除以二,我们将得到等效的预期长度值。

P.S。请原谅图片中的错误 "code points" 应该是 "code units"

我知道这是一个较老的问题,但我最近遇到了这个问题,这也可能对其他人有所帮助。

问题中的字符串:

$str = "✌️ @mention";

我希望它计数为 11:✌️{2},space{1},@{1},mention{7} 所以 2 + 1 + 1 + 7 = 11

PHP 的 intl 扩展有一个 grapheme_strlen() 函数,它以字素单位而不是字节或字符 (documentation)

获取字符串长度
<?php
$str = "✌️ @mention";
printf("strlen: %d" . PHP_EOL, strlen($str));
printf("mb_strlen UTF-8: %d" . PHP_EOL, mb_strlen($str, "UTF-8"));
printf("mb_strlen UTF-16: %d" . PHP_EOL, mb_strlen($str, "UTF-16"));
printf("iconv UTF-16: %d" . PHP_EOL, iconv_strlen(iconv("UTF-8", "UTF-16", $str)));
printf("iconv UTF-16: %d" . PHP_EOL, iconv_strlen(iconv("ISO-8859-1", "UTF-16", $str)));
printf("grapheme_strlen: %d" . PHP_EOL, grapheme_strlen($str));

PHP7.4 上的输出:

strlen: 27
mb_strlen UTF-8: 14
mb_strlen UTF-16: 13
PHP Notice:  iconv_strlen(): Detected an illegal character in input string in php shell code on line 1
iconv UTF-16: 0
PHP Notice:  iconv_strlen(): Detected an illegal character in input string in php shell code on line 1
iconv UTF-16: 0
grapheme_strlen: 11

或在这里尝试:https://www.tehplayground.com/s8e40DAslYX4Mozo