如何在PHP中迭代UTF-8字符串?
如何使用索引逐个字符迭代UTF-8字符串 使用括号运算符访问UTF-8字符串时,UTF编码字符由2个或更多元素组成 例如:如何在PHP中迭代UTF-8字符串?,php,utf-8,Php,Utf 8,如何使用索引逐个字符迭代UTF-8字符串 使用括号运算符访问UTF-8字符串时,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 = "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"
是否有其他方法可以在不使用mb_substr
的情况下逐个字符交互字符串?使用。它支持UTF-8 unicode
$chrArray = preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY);
您可以分析字符串的每个字节,并确定它是单个(ASCII)字符还是: UTF-8编码是可变宽度的,每个字符由1到4个字节表示。每个字节有0–4个前导连续的“1”位,后跟一个“0”位以指示其类型。2个或更多的“1”位表示多个字节序列中的第一个字节 您可以遍历字符串,而不是将位置增加1,而是完整读取当前字符,然后将位置增加该字符的长度 维基百科文章为每个字符提供了解释表:
在回答@Pekla和@Col.Shrapnel发表的评论时,我将
preg_split
与mb_substr
进行了比较
图像显示,preg_split
花费了1.2秒,而mb_substr
几乎花费了25秒
以下是函数的代码:
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;
}
函数拆分预处理($str){
返回preg_split('//u',$str,-1);
}
函数拆分\u mb($str){
$length=mb_strlen($str);
$chars=array();
对于($i=0;$iPreg-split将故障转移非常大的字符串,并出现内存异常,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;
}else{
$bytes = 4;
}
$str = substr($string, $pointer, $bytes);
$pointer += $bytes;
return $str;
}
}
使用它将字符串与下面的代码循环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;
函数microtime\u float(){
列表($usec,$sec)=爆炸('',微时间());
返回((浮动)$usec+(浮动)$sec);
}
$source='rvíztűrőtükörógépárv ztűrőtükörórórárárírőtükörórórérépárórérétürórőrórérérépáréróréréréréréréréréréréréréréréréréréréréréréré;
$t=数组(
0=>microtime_float()
);
对于($i=0;$i<10000;$i++){
$pointer=0;
while($chr=nextchar($source,$pointer))!==false){
//echo$chr;
}
}
$t[]=微时间浮点数();
echo$t[1]-$t[0].PHP_EOL.PHP_EOL;
我遇到了与OP相同的问题,我试图避免PHP中的正则表达式,因为它会失败,甚至会因长字符串而崩溃。我使用了一些更改,因为我将mbstring.func_重载设置为7
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;
}
}
函数nextchar($string,&$pointer,&$asciiPointer){
如果(!isset($string[$asciiPointer])返回false;
$char=ord($string[$asciiPointer]);
如果($char<128){
$pointer++;
返回$string[$asciiPointer++];
}否则{
如果($char<224){
$bytes=2;
}elseif($char<240){
$bytes=3;
}elseif($char<248){
$bytes=4;
}elseif($char=252){
$bytes=5;
}否则{
$bytes=6;
}
$str=substr($string,$pointer++,1);
$asciiPointer+=$bytes;
返回$str;
}
}
当mbstring.func_重载设置为7时,substr
实际上调用mb_substr
。因此substr
在这种情况下获得了正确的值。我必须添加第二个指针。一个指针跟踪字符串中的多字节字符,另一个指针跟踪单字节字符。多字节值用于substr
(因为它实际上是mb_substr
),而单字节值用于以这种方式检索字节:$string[$index]
显然,如果PHP决定修复[]访问以正确使用多字节值,这将失败。但是,这个修复从一开始就不需要。我认为最有效的解决方案是使用mb_substr处理字符串。在循环的每次迭代中,mb_substr将被调用两次(查找下一个字符和剩余的字符串)。它将只将剩余的字符串传递给下一次迭代。这样,每次迭代的主要开销将是查找下一个字符(完成两次),这只需要一到五次左右的操作,具体取决于字符的字节长度
如果这个描述不清楚,请告诉我,我将提供一个可用的PHP函数。以“奇妙的函数”为灵感,我创建了一个多字节字符串迭代器类
// 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]);
}
}
哪个会输出
K
ą
t
0: K
1: ą
3: t
或者如果你真的想知道起始字节的位置
foreach(new MbStrIterator("Kąt") as $i => $c) {
echo "{$i}: {$c}\n";
}
哪个会输出
K
ą
t
0: K
1: ą
3: t
定义“极其缓慢”。您是否分析了您的应用程序并发现这些mb_substr调用是一个瓶颈?在第二次阅读您的问题后,我意识到您想要一种不使用mb_substr的方法。我已删除了我的答案。@Col.Shrapnel:是的,50%的处理时间是由mb_substr
完成的。整个用户对web服务器的请求中,有50%的处理时间是由mb_substr
完成的ver,从连接到断开连接?我简直不敢相信。你的整个脚本在每个请求上都以相同的方式进行解析。没有人注意到这一点。你的mb解析占用了整个请求时间的哪一部分?我很惊讶没有其他人提出这一点,但是如果你想要最快的解决方案,并且可以承受高达4倍的字符串内存开销,那么u每个4字节的固定宽度字符-如果您需要随机访问字符串中的任何字符,这可能是最有效的解决方案,除非您处理非常大的文件,否则内存开销可能是可以接受的。这非常优雅,但我很难想象这比mb_substr()更快
@Pekka可能是这样。使用mb_substr
对str的长度是二次的
0: K
1: ą
3: t