截断 UTF-8 字符串以适应 PHP 中给定的字节数

问题描述 投票:0回答:6

假设我们有一个 UTF-8 字符串

$s
,我们需要缩短它,以便可以将其存储在 N 字节中。盲目地将其截断为 N 字节可能会弄乱它。但解码它以找到字符边界是一件很麻烦的事。有没有整齐的方法?

[编辑 20100414] 除了 S.Mark 的答案:

mb_strcut()
之外,我最近发现了另一个可以完成这项工作的函数:来自
intl
扩展的 grapheme_extract($s, $n, GRAPHEME_EXTR_MAXBYTES);。由于 intl 是 ICU 包装器,所以我对它很有信心。

php string unicode utf-8 truncate
6个回答
12
投票

我认为你不需要重新发明轮子,你可以只使用mb_strcut并确保首先将编码设置为UTF-8

mb_internal_encoding('UTF-8');
echo mb_strcut("\xc2\x80\xc2\x80", 0, 3); //from index 0, cut to 3 bytes.

它的回归

\xc2\x80

因为\xc2\x80\xc2中,最后一个是无效的


11
投票

编辑: S.Mark 的答案实际上比我的更好 - PHP 有一个(文档记录很差)内置函数可以准确解决这个问题。

原来的“回到位”答案如下:

  • 截断到所需的字节数
  • 如果最后一个字节以 110(二进制)开头,也将其删除
  • 如果倒数第二个字节以 1110(二进制)开头,则删除最后 2 个字节
  • 如果倒数第三个字节以 11110(二进制)开头,则删除最后 3 个字节

这可以确保末尾不会出现“不完整的字符”,这是截断 UTF-8 时可能出错的主要问题。 不幸的是(正如 Andrew 在评论中提醒我的那样)也存在两个单独编码的 Unicode 代码点形成单个字符的情况(基本上,诸如重音符号之类的变音符号可以表示为修改前面字母的单独代码点)。

处理这种事情需要高级的 Unicode-Fu,这在 PHP 中不可用,甚至可能不适用于所有情况(那里有一些

weird

脚本!),但幸运的是,它相对罕见,至少对于拉丁语来说-基于语言。


1
投票
我为此目的编写了这个简单的函数,但您需要

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回答显然是正确的方法。


1
投票
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"); } } ?>



1
投票
S.Mark

的答案是 mb_strcut() 之外,我最近发现了另一个函数可以完成类似的工作:来自

intl
扩展的
grapheme_extract($s, $n, GRAPHEME_EXTR_MAXBYTES);
功能有点不同:

mb_strcut()

文档声称它在最近的UTF-8字符边界处剪切,因此它不考虑多字符字素,而

grapheme_extract()
,otoh,则这样做。因此,根据您的需要,
grapheme_extract()
可能更好(例如显示字符串)或
mb_strcut()
可能更好(例如用于索引)。无论如何,尽管我会提到它。

(由于 intl 是 ICU 包装器,所以我对它很有信心。)


0
投票
没有。除了解码之外,没有其他方法可以做到这一点。

然而,编码非常机械。 请参阅维基百科文章中的漂亮表格 编辑:Michael Borgwardt 向我们展示了如何在不解码整个字符串的情况下做到这一点。 聪明。

© www.soinside.com 2019 - 2024. All rights reserved.