如何在PHP中迭代UTF-8string?

如何迭代一个UTF-8string字符使用索引?

使用括号操作符$str[0]访问UTF-8string时,utf编码的字符由2个或更多元素组成。

例如:

 $str = "Kąt"; $str[0] = "K"; $str[1] = " "; $str[2] = " "; $str[3] = "t"; 

但我想有:

 $str[0] = "K"; $str[1] = "ą"; $str[2] = "t"; 

这可能与mb_substr但这是非常缓慢的,即。

 mb_substr($str, 0, 1) = "K" mb_substr($str, 1, 1) = "ą" mb_substr($str, 2, 1) = "t" 

有没有另一种方法来整合string字符而不使用mb_substr

使用preg_split 。 用“u”修饰符它支持UTF-8 unicode。

 $chrArray = preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY); 

Preg split会破坏非常大的内存exception的string, mb_substr的确很慢,所以这里是一个简单而有效的代码,我敢肯定,你可以使用:

 function nextchar($string, &$pointer){ if(!isset($string[$pointer])) return false; $char = ord($string[$pointer]); if($char < 128){ return $string[$pointer++]; }else{ if($char < 224){ $bytes = 2; }elseif($char < 240){ $bytes = 3; }elseif($char < 248){ $bytes = 4; }elseif($char == 252){ $bytes = 5; }else{ $bytes = 6; } $str = substr($string, $pointer, $bytes); $pointer += $bytes; return $str; } } 

我用这个string来循环多字节string,如果我把它改成下面的代码,性能差异是巨大的:

 function nextchar($string, &$pointer){ if(!isset($string[$pointer])) return false; return mb_substr($string, $pointer++, 1, 'UTF-8'); } 

用下面的代码对一个string进行10000次循环,对第一个代码产生3秒的运行时间,对第二个代码产生13秒的运行时间:

 function microtime_float(){ list($usec, $sec) = explode(' ', microtime()); return ((float)$usec + (float)$sec); } $source = 'árvíztűrő tükörfúrógépárvíztűrő tükörfúrógépárvíztűrő tükörfúrógépárvíztűrő tükörfúrógépárvíztűrő tükörfúrógép'; $t = Array( 0 => microtime_float() ); for($i = 0; $i < 10000; $i++){ $pointer = 0; while(($chr = nextchar($source, $pointer)) !== false){ //echo $chr; } } $t[] = microtime_float(); echo $t[1] - $t[0].PHP_EOL.PHP_EOL; 

回答@Pekla和@Col发表的评论。 弹片我已经比较了preg_splitmb_substr

替代文字

图片显示, preg_split花了1.2s,而mb_substr差不多25s。

这里是function的代码:

 function split_preg($str){ return preg_split('//u', $str, -1); } function split_mb($str){ $length = mb_strlen($str); $chars = array(); for ($i=0; $i<$length; $i++){ $chars[] = mb_substr($str, $i, 1); } $chars[] = ""; return $chars; } 

使用Lajos Meszaros的奇妙function作为灵感,我创build了一个多字节的string迭代器类。

 // Multi-Byte String iterator class class MbStrIterator implements Iterator { private $iPos = 0; private $iSize = 0; private $sStr = null; // Constructor public function __construct(/*string*/ $str) { // Save the string $this->sStr = $str; // Calculate the size of the current character $this->calculateSize(); } // Calculate size private function calculateSize() { // If we're done already if(!isset($this->sStr[$this->iPos])) { return; } // Get the character at the current position $iChar = ord($this->sStr[$this->iPos]); // If it's a single byte, set it to one if($iChar < 128) { $this->iSize = 1; } // Else, it's multi-byte else { // Figure out how long it is if($iChar < 224) { $this->iSize = 2; } else if($iChar < 240){ $this->iSize = 3; } else if($iChar < 248){ $this->iSize = 4; } else if($iChar == 252){ $this->iSize = 5; } else { $this->iSize = 6; } } } // Current public function current() { // If we're done if(!isset($this->sStr[$this->iPos])) { return false; } // Else if we have one byte else if($this->iSize == 1) { return $this->sStr[$this->iPos]; } // Else, it's multi-byte else { return substr($this->sStr, $this->iPos, $this->iSize); } } // Key public function key() { // Return the current position return $this->iPos; } // Next public function next() { // Increment the position by the current size and then recalculate $this->iPos += $this->iSize; $this->calculateSize(); } // Rewind public function rewind() { // Reset the position and size $this->iPos = 0; $this->calculateSize(); } // Valid public function valid() { // Return if the current position is valid return isset($this->sStr[$this->iPos]); } } 

它可以像这样使用

 foreach(new MbStrIterator("Kąt") as $c) { echo "{$c}\n"; } 

哪个会输出

 K ą t 

或者如果你真的想知道起始字节的位置

 foreach(new MbStrIterator("Kąt") as $i => $c) { echo "{$i}: {$c}\n"; } 

哪个会输出

 0: K 1: ą 3: t 

您可以parsingstring的每个字节,并确定它是单个(ASCII)字符还是多字节字符的开头 :

UTF-8编码是可变宽度,每个字符由1到4个字节表示。 每个字节都有0-4个连续的“1”位,后跟一个“0”位来表示它的types。 2个或更多个“1”位表示该字节序列中的第一个字节。

你将穿过弦线,而不是增加1的位置,完全读取当前的字符,然后增加的位置的angular色长度。

维基百科的文章有每个人物的解释表[检索2010-10-01]

  0-127 Single-byte encoding (compatible with US-ASCII) 128-191 Second, third, or fourth byte of a multi-byte sequence 192-193 Overlong encoding: start of 2-byte sequence, but would encode a code point ≤ 127 ........ 

我和OP有同样的问题,我试图避免在PHP中使用正则expression式,因为它失败甚至崩溃与长string。 因为我把mbstring.func_overload设置为7,所以我用MészárosLajos的答案做了一些修改。

 function nextchar($string, &$pointer, &$asciiPointer){ if(!isset($string[$asciiPointer])) return false; $char = ord($string[$asciiPointer]); if($char < 128){ $pointer++; return $string[$asciiPointer++]; }else{ if($char < 224){ $bytes = 2; }elseif($char < 240){ $bytes = 3; }elseif($char < 248){ $bytes = 4; }elseif($char = 252){ $bytes = 5; }else{ $bytes = 6; } $str = substr($string, $pointer++, 1); $asciiPointer+= $bytes; return $str; } } 

随着mbstring.func_overload设置为7, substr实际上调用mb_substr 。 所以substr在这种情况下得到正确的值。 我不得不添加第二个指针。 一个跟踪string中的多字节字符,另一个跟踪单字节字符。 多字节值用于substr (因为它实际上是mb_substr ),而单字节值用于以这种方式检索字节: $string[$index]

很显然,如果PHP决定修改[]访问权限以适应多字节值,这将会失败。 而且,这个修复首先是不需要的。

我认为最有效的解决scheme是使用mb_substr来处理string。 在循环的每次迭代中,mb_substr将被调用两次(查找下一个字符和剩余的string)。 它只会传递剩余的string到下一个迭代。 这样,每次迭代的主要开销就是find下一个字符(完成两次),根据字符的字节长度,只需要一到五次操作。

如果这个描述不清楚,让我知道,我会提供一个PHP函数。