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

时间:2009-12-28 00:32:38

标签: php string unicode utf-8 truncate

假设我们有一个UTF-8字符串$s,我们需要缩短它,以便它可以存储在 N 个字节中。盲目地将其截断为 N 字节可能会搞砸它。但解码它以找到字符边界是一种拖累。有一种整洁的方式吗?

[编辑20100414]除了 S.Mark 的答案:mb_strcut()之外,我最近还找到了另一项功能:{b>来自 intl 扩展名。由于intl是一个ICU包装器,我对它很有信心。

6 个答案:

答案 0 :(得分:11)

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

原文“回到位”答案如下:

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

这可以确保您最后没有incomplete character悬空,这是截断UTF-8时可能出错的主要问题。

不幸的是(正如安德鲁在评论中提醒我的那样)还有两个单独编码的Unicode代码点形成单个字符的情况(基本上,像重音这样的变音符号可以表示为修改前一个字母的单独代码点)。

处理这种事情需要高级的Unicode-Fu,这在PHP中是不可用的,甚至可能不适用于所有情况(那里有somne​​ 奇怪的脚本!),但幸运的是它是相对的很少见,至少对于拉丁语言而言。

答案 1 :(得分:7)

我认为你不需要重新发明轮子,你可以使用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中,最后一个无效

答案 2 :(得分: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 answer显然是可行的方法。

答案 3 :(得分: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");
    }
}
?>

答案 4 :(得分:1)

除了 S.Mark 的回答是mb_strcut()之外,我最近还发现了另一个执行类似工作的功能:来自 intl <的grapheme_extract($s, $n, GRAPHEME_EXTR_MAXBYTES); / b>扩展名。

功能略有不同:mb_strcut()文档声称它在最近的UTF-8字符边界处切割,因此它不尊重多字符字形而grapheme_extract(),otoh。因此,根据您的需要,grapheme_extract()可能更好(例如显示字符串)或mb_strcut()可能更好(例如用于索引)。无论如何,只是我提到它。

(因为intl是一个ICU包装器,我对它有很大的信心。)

答案 5 :(得分:0)

<击>否。除了解码之外,没有办法做到这一点。编码是非常机械的。请参阅wikipedia article

中的漂亮表格

编辑:Michael Borgwardt告诉我们如何在不解码整个字符串的情况下完成它。聪明。