PHP:将多字节字符串(单词)拆分为单独的字符

16

尝试使用mb_split将字符串“主楼怎么走”分割成单个字符(我需要一个数组),但没有成功...有什么建议吗?

谢谢!


请查看http://stackoverflow.com/questions/1032674/string-to-array-and-back-php。 - Smandoli
6
请注意,这是一个多字节字符串。 - Peterim
5个回答

28

尝试使用带有'u'选项的正则表达式,例如:

  $chars = preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY);

同意Petr的说法。我尝试使用BIG5,但它不起作用! - Scott Chu
这不是我最喜欢的答案,因为解析和执行正则表达式需要比其他更“低级”的方法(例如使用mb_substr())更多的计算。无论如何,它适合这项工作。 - Valerio Bozz

10

一种丑陋的做法是:

mb_internal_encoding("UTF-8"); // this IS A MUST!! PHP has trouble with multibyte
                               // when no internal encoding is set!
$string = ".....";
$chars = array();
for ($i = 0; $i < mb_strlen($string); $i++ ) {
    $chars[] = mb_substr($string, $i, 1); // only one char to go to the array
}

你也可以尝试在使用mb_split之前设置它的内部编码。


1
mb_internal_encoding("UTF-8"); 这对我帮助很大。 - ivkremer
我喜欢这个答案,因为我曾经费尽心思地寻找一种简单的UTF-8安全方式来将文本分割成等长的部分。不仅是单个字符,而是部分。只需要编辑$i+5和5作为最后一个mb_substr参数,就可以将我的文本分成5个UTF8字符字符串。非常感谢。 - Jiro Matchonson
很棒的解决方案 :) - clarkk

4
您可以使用图形函数(PHP 5.3或intl 1.0)和IntlBreakIterator(PHP 5.5或intl 3.0)。以下代码展示了intl、mbstring和PCRE函数的区别。
// http://www.php.net/manual/function.grapheme-strlen.php
$string = "a\xCC\x8A"  // 'LATIN SMALL LETTER A WITH RING ABOVE' (U+00E5)
         ."o\xCC\x88"; // 'LATIN SMALL LETTER O WITH DIAERESIS'  (U+00F6)

$expected = ["a\xCC\x8A", "o\xCC\x88"];
$expected2 = ["a", "\xCC\x8A", "o", "\xCC\x88"];

var_dump(
    $expected === str_to_array($string),
    $expected === str_to_array2($string),
    $expected2 === str_to_array3($string),
    $expected2 === str_to_array4($string),
    $expected2 ===  str_to_array5($string)
);

function str_to_array($string)
{
    $length = grapheme_strlen($string);
    $ret = [];

    for ($i = 0; $i < $length; $i += 1) {
        $ret[] = grapheme_substr($string, $i, 1);
    }

    return $ret;
}

function str_to_array2($string)
{
    $it = IntlBreakIterator::createCharacterInstance('en_US');
    $it->setText($string);

    $ret = [];
    $prev = 0;

    foreach ($it as $pos) {

        $char = substr($string, $prev, $pos - $prev);

        if ('' !== $char) {
           $ret[] = $char;
        }

        $prev = $pos;
    }

    return $ret;
}

function str_to_array3($string)
{
    $it = IntlBreakIterator::createCodePointInstance();
    $it->setText($string);

    $ret = [];
    $prev = 0;

    foreach ($it as $pos) {

        $char = substr($string, $prev, $pos - $prev);

        if ('' !== $char) {
           $ret[] = $char;
        }

        $prev = $pos;
    }

    return $ret;
}

function str_to_array4($string)
{
    $length = mb_strlen($string, "UTF-8");
    $ret = [];

    for ($i = 0; $i < $length; $i += 1) {
        $ret[] = mb_substr($string, $i, 1, "UTF-8");
    }

    return $ret;
}

function str_to_array5($string) {
    return preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY);
}

当在生产环境中工作时,由于几乎所有字形和mbstring函数都无法处理无效的字节序列,因此您需要使用替换字符替换无效的字节序列。如果您有兴趣,请查看我的以往答案:https://dev59.com/IV3Va4cB1Zd3GeqPA3ko#13695364 如果您不考虑性能,则可以使用htmlspecialchars和htmlspecialchars_decode。这种方法的优点是支持除UTF-8之外的各种编码。
function str_to_array6($string, $encoding = 'UTF-8')
{
    $ret = [];
    str_replace_callback($string, function($char, $index) use (&$ret) { $ret[] = $char; return ''; }, $encoding);
    return $ret;
}

function str_replace_callback($string, $callable, $encoding = 'UTF-8')
{
    $str_size = strlen($string);
    $string = str_scrub($string, $encoding);

    $ret = '';
    $char = '';
    $index = 0;

    for ($pos = 0; $pos < $str_size; ++$pos) {

        $char .= $string[$pos];

        if (str_check_encoding($char, $encoding)) {

            $ret .= $callable($char, $index);
            $char = '';
            ++$index;
        }

    }

    return $ret;
}

function str_check_encoding($string, $encoding = 'UTF-8')
{
    $string = (string) $string;
    return $string === htmlspecialchars_decode(htmlspecialchars($string, ENT_QUOTES, $encoding));
}

function str_scrub($string, $encoding = 'UTF-8')
{
    return htmlspecialchars_decode(htmlspecialchars($string, ENT_SUBSTITUTE, $encoding));
}

如果你想学习UTF-8的规范,字节操作是一个很好的练习方式。

function str_to_array6($string)
{
    // REPLACEMENT CHARACTER (U+FFFD)
    $substitute = "\xEF\xBF\xBD";
    $size = strlen($string);
    $ret = [];

    for ($i = 0; $i < $size; $i += 1) {

        if ($string[$i] <= "\x7F") {

            $ret[] = $string[$i];

        } elseif ("\xC2" <= $string[$i] && $string[$i] <= "\xDF")  {

            if (!isset($string[$i+1])) {

                $ret[] = $substitute;
                return $ret;

            } elseif ($string[$i+1] < "\x80" || "\xBF" < $string[$i+1]) {

                $ret[] = $substitute;

            } else {

                $ret[] = substr($string, $i, 2);
                $i += 1;

            }

        } elseif ("\xE0" <= $string[$i] && $string[$i] <= "\xEF") {

            $left = "\xE0" === $string[$i] ? "\xA0" : "\x80";
            $right = "\xED" === $string[$i] ? "\x9F" : "\xBF";

            if (!isset($string[$i+1])) {

                $ret[] = $substitute;
                return $ret;

            } elseif ($string[$i+1] < $left || $right < $string[$i+1]) {

                $ret[] = $substitute;

            } else {

                if (!isset($string[$i+2])) {

                    $ret[] = $substitute;
                    return $ret;

                } elseif ($string[$i+2] < "\x80" || "\xBF" < $string[$i+2]) {

                    $ret[] = $substitute;
                    $i += 1;

                } else {

                    $ret[] = substr($string, $i, 3);
                    $i += 2;

                }

            }

        } elseif ("\xF0" <= $string[$i] && $string[$i] <= "\xF4") {

            $left = "\xF0" === $string[$i] ? "\x90" : "\x80";
            $right = "\xF4" === $string[$i] ? "\x8F" : "\xBF";

            if (!isset($string[$i+1])) {

                $ret[] = $substitute;
                return $ret;

            } elseif ($string[$i+1] < $left || $right < $string[$i+1]) {

                $ret[] = $substitute;

            } else {

                if (!isset($string[$i+2])) {

                    $ret[] = $substitute;
                    return $ret;

                } elseif ($string[$i+2] < "\x80" || "\xBF" < $string[$i+2]) {

                    $ret[] = $substitute;
                    $i += 1;

                } else {

                    if (!isset($string[$i+3])) {

                        $ret[] = $substitute;
                        return $ret;

                    } elseif ($string[$i+3] < "\x80" || "\xBF" < $string[$i+3]) {

                        $ret[] = $substitute;
                        $i += 2;

                    } else {

                        $ret[] = substr($string, $i, 4);
                        $i += 3;

                    }

                }

            }

        } else {

            $ret[] = $substitute;

        }

    }

    return $ret;

}

这些函数之间的基准测试结果在此处。
grapheme
0.12967610359192
IntlBreakIterator::createCharacterInstance
0.17032408714294
IntlBreakIterator::createCodePointInstance
0.079245090484619
mbstring
0.081080913543701
preg_split
0.043133974075317
htmlspecialchars
0.25599694252014
byte maniplulation
0.13132810592651

基准测试代码在这里。
$string = '主楼怎么走';

foreach (timer([
    'grapheme' => 'str_to_array',
    'IntlBreakIterator::createCharacterInstance' => 'str_to_array2',
    'IntlBreakIterator::createCodePointInstance' => 'str_to_array3',
    'mbstring' => 'str_to_array4',
    'preg_split' => 'str_to_array5',
    'htmlspecialchars' => 'str_to_array6',
    'byte maniplulation' => 'str_to_array7'
],
[$string]) as $desc => $time) {

  echo $desc, PHP_EOL,
       $time, PHP_EOL; 
}

function timer(array $callables, array $arguments, $repeat = 10000) {

    $ret = [];
    $save = $repeat;

    foreach ($callables as $key => $callable) {

        $start = microtime(true);

        do {

            array_map($callable, $arguments);

        } while($repeat -= 1);

        $stop = microtime(true);
        $ret[$key] = $stop - $start;
        $repeat = $save;

    }

    return $ret;
}

1

str_split()的文档说明如下:

注意:

当处理多字节编码字符串时,str_split()将拆分为字节而不是字符。

PHP 7.4.0新增了一个函数mb_str_split()。请使用它代替。


0
假设您已经设置了所需的编码和 MB 函数的正则表达式编码(例如 UTF-8),您可以使用我的字符串类库中的方法。
/**
 * Splits a string into pieces (on whitespace by default).
 ^
 * @param string $pattern
 * @param string $target
 * @param int $limit
 * @return array
 */
public function split(string $target, string $pattern = '\s+', int $limit = -1): array
{
    return mb_split($pattern, $target, $limit);
}

通过将mb_split()函数封装在一个方法中,可以使其更易于使用。只需使用所需的变量$pattern调用它即可。
请记住,为您的任务适当设置字符编码。
mb_internal_encoding('UTF-8');  // For example.
mb_regex_encoding('UTF-8');     // For example.

在我的包装方法中,像这样向该方法提供空字符串。
$string = new String('UTF-8', 'UTF-8');  // Sets the internal and regex encodings.
$string->split($yourString, "")

在直接的 PHP 情况下...
$characters = mb_split("", $string);

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