假设我们有一个 UTF-8 字符串$s
,我们需要将其缩短以便可以存储在N个字节中。盲目地将其截断为N个字节可能会搞砸。但是解码它以找到字符边界是一件麻烦事。有没有整洁的方法?
[编辑 20100414] 除了S.Mark的回答:mb_strcut()
,我最近发现了另一个功能来完成这项工作:grapheme_extract($s, $n, GRAPHEME_EXTR_MAXBYTES);
来自intl扩展。由于 intl 是 ICU 包装器,所以我对它很有信心。
编辑: S.Mark 的答案实际上比我的要好 - PHP 有一个(记录不充分的)内置函数可以完全解决这个问题。
原始的“回到原点”答案如下:
这可确保您最后没有不完整的字符悬空,这是截断 UTF-8 时可能出错的主要问题。
不幸的是(正如 Andrew 在评论中提醒我的那样),也有两个单独编码的 Unicode 代码点形成一个字符的情况(基本上,诸如重音之类的变音符号可以表示为修改前一个字母的单独代码点)。
处理这种事情需要高级的 Unicode-Fu,这在 PHP 中是不可用的,甚至可能不适用于所有情况(那里有一些奇怪的脚本!),但幸运的是它相对罕见,至少对于基于拉丁语的语言来说是这样。
我认为您不需要重新发明轮子,您可以使用mb_strcut并确保首先将编码设置为UTF-8。
mb_internal_encoding('UTF-8');
echo mb_strcut("\xc2\x80\xc2\x80", 0, 3); //from index 0, cut 3 characters.
它的回报
\xc2\x80
因为在\xc2\x80\xc2中,最后一个无效
我为此目的编写了这个简单的函数,但你需要mb_string。
function str_truncate($string, $bytes = null)
{
if (isset($bytes) === true)
{
// to speed things up
$string = mb_substr($string, 0, $bytes, 'UTF-8');
while (strlen($string) > $bytes)
{
$string = mb_substr($string, 0, -1, 'UTF-8');
}
}
return $string;
}
虽然这段代码也有效,但 S.Mark 的答案显然是要走的路。
这是一个测试mb_strcut()
。它并不能证明它确实符合我们的要求,但我觉得它很有说服力。
<?php
ini_set('default_charset', 'UTF-8' );
$strs = array(
'Iñtërnâtiônàlizætiøn',
'החמאס: רוצים להשלים את עסקת שליט במהירות האפשרית',
'ايران لا ترى تغييرا في الموقف الأمريكي',
'独・米で死傷者を出した銃の乱射事件',
'國會預算處公布驚人的赤字數據後',
'이며 세계 경제 회복에 걸림돌이 되고 있다',
'В дагестанском лесном массиве южнее села Какашура',
'นายประสิทธิ์ รุ่งสะอาด ปลัดเทศบาล รักษาการแทนนายกเทศมนตรี ต.ท่าทองใหม่',
'ભારતીય ટીમનો સુવર્ણ યુગ : કિવીઝમાં પણ કમાલ',
'ཁམས་དཀར་མཛེས་ས་ཁུལ་དུ་རྒྱ་གཞུང་ལ་ཞི་བའི་ངོ་རྒོལ་',
'Χιόνια, βροχές και θυελλώδεις άνεμοι συνθέτουν το',
'Հայաստանում սկսվել է դատական համակարգի ձեւավորումը',
'რუსეთი ასევე გეგმავს სამხედრო');
for ( $i = 10; $i <= 30; $i += 5 ) {
foreach ($strs as $s) {
$t = mb_strcut($s, 0, $i, 'UTF-8');
print(
sprintf('%3s%3s ', mb_strlen($t, 'UTF-8'), mb_strlen($t, 'latin1'))
. ( mb_check_encoding($t, 'UTF-8') ? ' OK ' : ' Bad ' )
. $t . "\n");
}
}
?>
除了S.Mark的回答是mb_strcut()
,我最近发现了另一个函数来做类似的工作:grapheme_extract($s, $n, GRAPHEME_EXTR_MAXBYTES);
来自intl扩展。
功能有点不同:mb_strcut()
文档声称它在最近的 UTF-8 字符边界处进行切割,因此它不尊重多字符字素,而grapheme_extract()
otoh 则这样做。因此,根据您的需要,grapheme_extract()
可能会更好(例如显示字符串)或mb_strcut()
可能更好(例如索引)。无论如何,尽管我会提到它。
(而且由于 intl 是 ICU 包装器,我对它很有信心。)
不,除了解码之外没有其他方法可以做到这一点。然而,编码是相当机械的。请参阅维基百科文章中的漂亮表格
编辑:Michael Borgwardt 向我们展示了如何在不解码整个字符串的情况下做到这一点。聪明的。