在PHP中将字符串拆分为Unicode字符数组的最佳方法是什么?

20

在PHP中,将字符串拆分为Unicode字符数组的最佳方法是什么?如果输入不一定是UTF-8?

我想知道输入字符串中的Unicode字符集是否是另一个Unicode字符集的子集。

为什么不直接使用 mb_ 函数族,而前面几个答案没有使用它们?


1
你是否意识到比较Unicode字符并不是一件简单的事情,这取决于你想要进行哪种类型的比较?例如,你可以将ü写成U+00DC或U+0075 U+0308。 - derobert
是的,我确实意识到了这一点。如果出现问题,那么在拆分之前我需要将输入转换为Unicode正规形式之一。 - joeforker
自 PHP 7.4 开始,有 mb_ 函数可用。 - Patryk Godowski
8个回答

23
您可以在PCRE正则表达式中使用'u'修饰符;请参见“模式修饰符”(引用):http://php.net/manual/en/reference.pcre.pattern.modifiers.php

u(PCRE8)

此修饰符打开了PCRE的附加功能,与Perl不兼容。将模式字符串视为UTF-8。此修饰符可从Unix上的PHP 4.1.0或更高版本以及win32上的PHP 4.2.3开始使用。自PHP 4.3.5以来,检查模式的UTF-8有效性。

例如,考虑以下代码:
header('Content-type: text/html; charset=UTF-8');  // So the browser doesn't make our lives harder
$str = "abc 文字化け, efg";

$results = array();
preg_match_all('/./', $str, $results);
var_dump($results[0]);

您将会得到一个无法使用的结果:

array
  0 => string 'a' (length=1)
  1 => string 'b' (length=1)
  2 => string 'c' (length=1)
  3 => string ' ' (length=1)
  4 => string '�' (length=1)
  5 => string '�' (length=1)
  6 => string '�' (length=1)
  7 => string '�' (length=1)
  8 => string '�' (length=1)
  9 => string '�' (length=1)
  10 => string '�' (length=1)
  11 => string '�' (length=1)
  12 => string '�' (length=1)
  13 => string '�' (length=1)
  14 => string '�' (length=1)
  15 => string '�' (length=1)
  16 => string ',' (length=1)
  17 => string ' ' (length=1)
  18 => string 'e' (length=1)
  19 => string 'f' (length=1)
  20 => string 'g' (length=1)

但是,使用这段代码:

header('Content-type: text/html; charset=UTF-8');  // So the browser doesn't make our lives harder
$str = "abc 文字化け, efg";

$results = array();
preg_match_all('/./u', $str, $results);
var_dump($results[0]);

(注意正则表达式结尾处的 'u')

你会得到你想要的:

array
  0 => string 'a' (length=1)
  1 => string 'b' (length=1)
  2 => string 'c' (length=1)
  3 => string ' ' (length=1)
  4 => string '文' (length=3)
  5 => string '字' (length=3)
  6 => string '化' (length=3)
  7 => string 'け' (length=3)
  8 => string ',' (length=1)
  9 => string ' ' (length=1)
  10 => string 'e' (length=1)
  11 => string 'f' (length=1)
  12 => string 'g' (length=1)

希望这可以帮到你:-)

用户或 PHP 7.4 或更高版本 - 滚动到 答案,使用 mb_str_split() - William Turrell

14
稍微比 preg_match_all 简单:
preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY)

这将为您提供一个由字符组成的一维数组,无需使用匹配对象。


这个答案是最有意义的,即逻辑上的目标是分割,我们不关心匹配每个字符(尽管在后台可能会这样做)。我本来要用你的解决方案回答这个问题,但有一个小区别:限制(第三个参数)可以使用NULL而不是-1,因为“-10NULL表示“没有限制”,并且与PHP中的标准相同,您可以使用NULL跳过标志参数。 - Armfoot

8

值得一提的是,自PHP 7.4起,有一个内置函数mb_str_split可以完成此操作。

$chars = mb_str_split($str);

preg_split('//u', $str)不同的是,此函数支持除UTF-8之外的其他编码。


所以,让我们点赞这个答案。 :) @joeforker,也许将这个答案标记为正确的? - Patryk Godowski
这应该是“现代”的答案。它适用于“preg_split( '//u", $string, -1, PREG_SPLIT_NO_EMPTY)"无法解决的情况。 - scott8035

6

试试这个:

preg_match_all('/./u', $text, $array);

1

如果正则表达式的方式不够用,我曾经编写了一个被抛弃但可能会在您自行编写时有所帮助的 Zend_Locale_UTF8

特别是看一下类Zend_Locale_UTF8_PHP5_String,它可以读取Unicode字符串并将其分割成单个字符(显然可能由多个字节组成)。

编辑: 我刚刚意识到ZF的svn浏览器已经崩溃了,所以我复制了重要的方法以供参考:

/**
 * Returns the UTF-8 code sequence as an array for any given $string.
 *
 * @access protected
 * @param string|integer $string
 * @return array
 */
protected function _decode( $string ) {

    $string     = (string) $string;
    $length     = strlen($string);
    $sequence   = array();

    for ( $i=0; $i<$length; ) {
        $bytes      = $this->_characterBytes($string, $i);
        $ord        = $this->_ord($string, $bytes, $i);

        if ( $ord !== false )
            $sequence[] = $ord;

        if ( $bytes === false )
            $i++;
        else
            $i  += $bytes;
    }

    return $sequence;

}

/**
 * Returns the UTF-8 code of a character.
 *
 * @see http://en.wikipedia.org/wiki/UTF-8#Description
 * @access protected
 * @param string $string
 * @param integer $bytes
 * @param integer $position
 * @return integer
 */
protected function _ord( &$string, $bytes = null, $pos=0 )
{
    if ( is_null($bytes) )
        $bytes = $this->_characterBytes($string);

    if ( strlen($string) >= $bytes ) {

        switch ( $bytes ) {
            case 1:
                return ord($string[$pos]);
                break;

            case 2:
                return  ( (ord($string[$pos])   & 0x1f) << 6 ) +
                        ( (ord($string[$pos+1]) & 0x3f) );
                break;

            case 3:
                return  ( (ord($string[$pos])   & 0xf)  << 12 ) + 
                        ( (ord($string[$pos+1]) & 0x3f) << 6 ) +
                        ( (ord($string[$pos+2]) & 0x3f) );
                break;

            case 4:
                return  ( (ord($string[$pos])   & 0x7)  << 18 ) + 
                        ( (ord($string[$pos+1]) & 0x3f) << 12 ) + 
                        ( (ord($string[$pos+1]) & 0x3f) << 6 ) +
                        ( (ord($string[$pos+2]) & 0x3f) );
                break;

            case 0:
            default:
                return false;
        }
    }

    return false;
}
/**
 * Returns the number of bytes of the $position-th character.
 *
 * @see http://en.wikipedia.org/wiki/UTF-8#Description
 * @access protected
 * @param string $string
 * @param integer $position
 */
protected function _characterBytes( &$string, $position = 0 ) {
    $char       = $string[$position];
    $charVal    = ord($char);

    if ( ($charVal & 0x80) === 0 )
        return 1;

    elseif ( ($charVal & 0xe0) === 0xc0 )
        return 2;

    elseif ( ($charVal & 0xf0) === 0xe0 )
        return 3;

    elseif ( ($charVal & 0xf8) === 0xf0)
        return 4;
    /*
    elseif ( ($charVal & 0xfe) === 0xf8 )
        return 5;
    */

    return false;
}

1
function str_split_unicode($str, $l = 0) {
    if ($l > 0) {
        $ret = array();
        $len = mb_strlen($str, "UTF-8");
        for ($i = 0; $i < $len; $i += $l) {
            $ret[] = mb_substr($str, $i, $l, "UTF-8");
        }
        return $ret;
    }
    return preg_split("//u", $str, -1, PREG_SPLIT_NO_EMPTY);
}
var_dump(str_split_unicode("لأآأئؤة"));

输出:

array (size=7)
  0 => string 'ل' (length=2)
  1 => string 'أ' (length=2)
  2 => string 'آ' (length=2)
  3 => string 'أ' (length=2)
  4 => string 'ئ' (length=2)
  5 => string 'ؤ' (length=2)
  6 => string 'ة' (length=2)

了解更多信息:http://php.net/manual/en/function.str-split.php


0
最佳分割方式:我刚刚修改了Laravel的str_limit()函数。
    public static function split_text($text, $limit = 100, $end = '')
{
    $width=mb_strwidth($text, 'UTF-8');
    if ($width <= $limit) {
        return $text;
    }
    $res=[];
    for($i=0;$i<=$width;$i=$i+$limit){
        $res[]=rtrim(mb_strimwidth($text, $i, $limit, '', 'UTF-8')).$end;
    }
     return $res;
}

0

我能够使用mb_*编写一个解决方案,其中包括一次UTF-16的转换,以及回到原来的格式,可能是为了加快字符串索引的速度。

$japanese2 = mb_convert_encoding($japanese, "UTF-16", "UTF-8");
$length = mb_strlen($japanese2, "UTF-16");
for($i=0; $i<$length; $i++) {
    $char = mb_substr($japanese2, $i, 1, "UTF-16");
    $utf8 = mb_convert_encoding($char, "UTF-8", "UTF-16");
    print $utf8 . "\n";
}

我在避免使用mb_internal_encoding并且只是在每个mb_*调用中指定所有内容时运气更好。我相信我最终会使用preg解决方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接