PHP 大会日本 2024

多字节字符串函数

参考

多字节字符编码方案及其相关问题相当复杂,超出了本文档的范围。有关这些主题的更多信息,请参考以下网址和其他资源。

目录

添加笔记

用户贡献笔记 35 条笔记

69
deceze at gmail dot com
12 年前
请注意,评论中关于 mb_str_replace 的所有讨论都毫无意义。str_replace 可以很好地处理多字节字符串

<?php

$string
= '漢字はユニコード';
$needle = 'は';
$replace = 'Foo';

echo
str_replace($needle, $replace, $string);
// 输出:漢字Fooユニコード

?>

通常的问题是字符串被评估为二进制字符串,这意味着 PHP 完全没有意识到编码。如果您从某个地方(数据库、POST 请求)获取值,并且搜索字符串和目标字符串的编码不同,则会出现问题。这通常意味着源代码的保存编码与您从外部接收到的编码不同。因此,二进制表示不匹配,什么也不会发生。
21
Eugene Murai
19 年前
PHP 可以输入和输出 Unicode,但与 Microsoft 的含义略有不同:当 Microsoft 说“Unicode”时,它隐含地指的是带 BOM 的小端 UTF-16(FF FE = chr(255).chr(254)),而 PHP 的“UTF-16”指的是带 BOM 的大端。因此,PHP 似乎无法为 Microsoft Excel 输出 Unicode CSV 文件。解决这个问题很简单:只需在 UTF-16LE 字符串前面添加 BOM。

示例

$unicode_str_for_Excel = chr(255).chr(254).mb_convert_encoding( $utf8_str, 'UTF-16LE', 'UTF-8');
13
mdoocy at u dot washington dot edu
17 年前
请注意,一些多字节函数以 O(n) 时间运行,而不是像单字节等效函数那样以常数时间运行。这包括任何需要访问特定索引的功能,因为在字节数不一定与字符数匹配的字符串中无法进行随机访问。受影响的函数包括:mb_substr()、mb_strstr()、mb_strcut()、mb_strpos() 等。
6
treilor at gmail dot com
10年前
对于那些将遵循rawsrc at gmail dot com建议的人,一个小小的提示:mb_split使用正则表达式,在这种情况下,使用内置函数mb_ereg_replace可能更有意义。
11
匿名
11年前
另一个单行mb_trim()函数

<?php
function mb_trim($string, $trim_chars = '\s'){
return
preg_replace('/^['.$trim_chars.']*(?U)(.*)['.$trim_chars.']*$/u', '\\1',$string);
}
$string = ' "some text." ';
echo
mb_trim($string, '\s".');
//some text
?>
4
mattr at telebody dot com
10年前
关于Daniel Rhodes的mb_punctuation_trim()的一个简短说明。
正则表达式修饰符u并不表示非贪婪,而是表示模式使用UTF-8编码。应使用U修饰符来获得非贪婪行为。(我尚未测试他的代码。)
参见 https://php.net/manual/en/reference.pcre.pattern.modifiers.php
5
Hayley Watson
6年前
某些多字节编码可以安全地用于str_replace()等函数,而另一些则不能。仅仅确保所有涉及的字符串使用相同的编码是不够的:显然它们必须相同,但这还不够。它必须是正确的编码。

UTF-8是安全的编码之一,因为它被设计成明确地表示每个编码字符在构成编码文本的字节串中开始和结束的位置。有些编码是不安全的:文本中一个字符的最后几个字节后跟下一个字符的第一个几个字节,两者合在一起可能构成一个有效的字符。str_replace() 对“字符”、“字符编码”或“编码文本”一无所知。它只知道字节串。对于str_replace(),两个具有双字节编码的相邻字符看起来只是四个字节的序列,它不会知道不应该尝试匹配中间的两个字节。

虽然可以找到str_replace() 损坏文本的真实案例,但这可以通过使用HTML-ENTITIES编码来说明。它不是安全的编码之一。传递给str_replace()的所有字符串都是有效的HTML-ENTITIES编码文本,因此满足“所有输入使用相同的编码”规则。

文本是“x<y”。它由字节字符串[78 26 6c 74 3b 79]表示。请注意,文本有三个字符,但字符串有六个字节。

<?php

$string
= 'x&lt;y';
mb_internal_encoding('HTML-ENTITIES');

echo
"文本长度: ", mb_strlen($string), "\t字符串长度: ", strlen($string), " ... ", $string, "\n";
// 三个字符,六个字节;文本显示为“x<y”。

$newstring = str_replace('l', 'g', $string);
echo
"文本长度: ", mb_strlen($newstring), "\t字符串长度: ", strlen($newstring), " ... ", $newstring, "\n";
// 三个字符,六个字节,但现在文本显示为“x>y”;错误的字符已更改。

$newstring = str_replace(';', ':', $string);
echo
"文本长度: ", mb_strlen($newstring), "\t字符串长度: ", strlen($newstring), " ... ", $newstring, "\n";
// 现在甚至文本长度也错误,文本已损坏。

?>

即使“l”和“;”都不出现在文本“x<y”中,str_replace()仍然找到并更改了字节。在一个例子中,它将文本更改为“x>y”,而在另一个例子中,它完全破坏了编码。

我想,这是另一个理由,如果可以的话,使用UTF-8。
7
mitgath at gmail dot com
15年前
根据
http://bugs.php.net/bug.php?id=21317
这是缺失的函数

<?php
function mb_str_pad ($input, $pad_length, $pad_string, $pad_style, $encoding="UTF-8") {
return
str_pad($input,
strlen($input)-mb_strlen($input,$encoding)+$pad_length, $pad_string, $pad_style);
}
?>
8
roydukkey at roydukkey dot com
15年前
这将是创建多字节substr_replace函数的一种方法

<?php
function mb_substr_replace($output, $replace, $posOpen, $posClose) {
return
mb_substr($output, 0, $posOpen).$replace.mb_substr($output, $posClose+1);
}
?>
6
Ben XO
16年前
PHP5没有mb_trim(),所以我创建了一个。它的功能与trim()完全相同,但额外增加了PCRE字符类(当然,包括所有有用的Unicode字符类,例如\pZ)。

与我见过的其他解决这个问题的方法不同,我希望模拟trim()的全部功能——特别是自定义字符列表的能力。

<?php
/**
* 以多字节友好的方式修剪字符串两端(或任意一端)的字符。
*
* 在大多数情况下,此函数的行为与 trim() 完全相同:例如,提供 'abc' 作为
* charlist 将从字符串中修剪所有 'a'、'b' 和 'c' 字符,当然,额外的好处是您可以在 charlist 中使用 Unicode 字符。
*
* 我们使用 PCRE 字符类以 Unicode 识别的
* 方式进行修剪,因此我们必须转义 ^、\、- 和 ],因为它们在此处具有特殊含义。
* 正如您所料,charlist 中的单个 \ 被解释为
* “修剪反斜杠”(并相应地转义为双 \)。在大多数情况下
* 你可以忽略这个细节。
*
* 但作为额外功能,我们还允许 PCRE 特殊字符类(例如 '\s')
* 因为它们在处理 UCS 时非常有用。例如 '\pZ',
* 匹配 Unicode 中定义的每个“分隔符”字符,包括不换行
* 和零宽空格。
*
* 在字符类中使用两个或多个相同的字符是没有意义的,因此我们将字符列表中的双 \ 解释为
* 正则表达式中的单个 \,允许您安全地混合普通字符和 PCRE
* 特殊类。
*
* 使用此额外功能时*请小心*,因为 PHP 也会在正则表达式看到反斜杠之前将其解释为
* 转义字符。因此,要在正则表达式中指定 '\\s'(这将转换为特殊字符
* 类 '\s' 用于修剪),您通常需要在
* PHP 代码中使用*4* 个反斜杠 - 正如您可以从 $charlist 的默认值中看到的那样。
*
* @param string
* @param charlist 要从字符串两端移除的字符列表。
* @param boolean 修剪左侧?
* @param boolean 修剪右侧?
* @return String
*/
function mb_trim($string, $charlist='\\\\s', $ltrim=true, $rtrim=true)
{
$both_ends = $ltrim && $rtrim;

$char_class_inner = preg_replace(
array(
'/[\^\-\]\\\]/S', '/\\\{4}/S' ),
array(
'\\\\\\0', '\\' ),
$charlist
);

$work_horse = '[' . $char_class_inner . ']+';
$ltrim && $left_pattern = '^' . $work_horse;
$rtrim && $right_pattern = $work_horse . '$';

if(
$both_ends)
{
$pattern_middle = $left_pattern . '|' . $right_pattern;
}
elseif(
$ltrim)
{
$pattern_middle = $left_pattern;
}
else
{
$pattern_middle = $right_pattern;
}

return
preg_replace("/$pattern_middle/usSD", '', $string) );
}
?>
6
kamiware.org邮箱用户
8年前
str_replace 不是多字节安全的。

这个乌克兰语单词在下面的代码中会导致错误:відео

$rubishcharacters='[#|\[{}\]´`≠,;.:-\\_<>=*+"\'?()!§$&%';
$searchstring='відео';

$result = str_replace(str_split($rubishcharacters), ' ', $searchstring);
2
gmail.com邮箱用户
5年前
如果你的项目很大,迁移到 MB 函数可能会有点痛苦。我们公司花了一段时间才完成,然后我们编写了一个小脚本并在博客中进行了说明。
https://link.medium.com/25w1LronCX

这使得迁移到 mb_ 函数变得非常容易。
2
Daniel Rhodes
11年前
这是一个简单快捷的函数,用于从任何语言的 UTF-8 字符串中删除开头和结尾的*标点符号*(更具体地说,“非单词字符”)。(至少它对日语和英语都足够有效。)

/**
* 从字符串的开头和结尾修剪单字节和多字节标点符号
*
* @author Daniel Rhodes
* @note 我们希望第一个非单词抓取是贪婪的,但是
* @note 我们希望点星抓取(在最后一个非单词抓取之前)
* @note 是非贪婪的
*
* @param string $string UTF-8 编码的输入字符串
* @return string 与 $string 相同,但已删除开头和结尾的标点符号
*/
function mb_punctuation_trim($string)
{
preg_match('/^[^\w]{0,}(.*?)[^\w]{0,}$/iu', $string, $matches); //不区分大小写且非贪婪

if(count($matches) < 2)
{
//出现一些奇怪的错误,所以只返回原始输入
return $string;
}

return $matches[1];
}

希望您喜欢!
1
d4k.net邮箱用户
15年前
我希望这个 mb_str_replace 对数组有效。如果您需要更改编码,请事先使用 mb_internal_encoding()。

感谢 ermshaus.org邮箱用户 marc 提供的原始代码。

<?php

if(!function_exists('mb_str_replace')) {

function
mb_str_replace($search, $replace, $subject) {

if(
is_array($subject)) {
$ret = array();
foreach(
$subject as $key => $val) {
$ret[$key] = mb_str_replace($search, $replace, $val);
}
return
$ret;
}

foreach((array)
$search as $key => $s) {
if(
$s == '') {
continue;
}
$r = !is_array($replace) ? $replace : (array_key_exists($key, $replace) ? $replace[$key] : '');
$pos = mb_strpos($subject, $s);
while(
$pos !== false) {
$subject = mb_substr($subject, 0, $pos) . $r . mb_substr($subject, $pos + mb_strlen($s));
$pos = mb_strpos($subject, $s, $pos + mb_strlen($r));
}
}

return
$subject;

}

}

?>
5
gmail.com邮箱用户
13年前
你好,

对于那些正在寻找 mb_str_replace 的人,这里有一个简单的函数
<?php
function mb_str_replace($needle, $replacement, $haystack) {
return
implode($replacement, mb_split($needle, $haystack));
}
?>
我没有找到更简单的处理方法 :-)
1
NOSPAMmte.biglobe.ne.jp邮箱用户
19 年前
一位朋友指出,mbstring 页面上的表 1 中的条目
“mbstring.http_input PHP_INI_ALL”似乎是错误的:在示例 4 上方,它说“无法从 PHP 脚本控制 HTTP 输入字符转换。要禁用 HTTP 输入字符转换,必须在 php.ini 中进行。”
此外,该表显示了旧版 PHP 的默认值
;; 禁用 HTTP 输入转换
mbstring.http_input = pass *但是*(对于 PHP 4.3.0 或更高版本)
;; 禁用 HTTP 输入转换
mbstring.encoding_translation = Off
1
gmail.com邮箱用户
7年前
substr_replace 函数的多字节版本
(受 roydukkey 的注释启发,并进行了一些更正)

function mb_substr_replace($string, $replacement, $start, $length){
return mb_substr($string, 0, $start).$replacement.mb_substr($string, $start+$length);
}
1
Daniel Rhodes
11年前
这是一个简单快捷的函数,用于从任何语言的 UTF-8 字符串中删除开头和结尾的*标点符号*(更具体地说,“非单词字符”)。(至少它对日语和英语都足够有效。)

/**
* 从字符串的开头和结尾修剪单字节和多字节标点符号
*
* @author Daniel Rhodes
* @note 我们希望第一个非单词抓取是贪婪的,但是
* @note 我们希望点星抓取(在最后一个非单词抓取之前)
* @note 是非贪婪的
*
* @param string $string UTF-8 编码的输入字符串
* @return string 与 $string 相同,但已删除开头和结尾的标点符号
*/
function mb_punctuation_trim($string)
{
preg_match('/^[^\w]{0,}(.*?)[^\w]{0,}$/iu', $string, $matches); //不区分大小写且非贪婪

if(count($matches) < 2)
{
//出现一些奇怪的错误,所以只返回原始输入
return $string;
}

return $matches[1];
}

希望您喜欢!
0
live.de邮箱用户
7年前
来自“mt at mediamedics dot nl”的建议并不像反对票显示的那样糟糕。只有一个小的bug,很容易修复。
需要修改“for”循环的头,将`$i + $split_length`替换为`$i += $split_length`。

这是完整的可运行代码,其中添加了检查以验证方法是否已存在。

<?php
if ( !function_exists('mb_str_split') )
{
function
mb_str_split($string, $split_length = 1)
{
mb_internal_encoding('UTF-8');
mb_regex_encoding('UTF-8');

$split_length = ($split_length <= 0) ? 1 : $split_length;

$mb_strlen = mb_strlen($string, 'utf-8');

$array = array();

for(
$i = 0; $i < $mb_strlen; $i += $split_length)
{
$array[] = mb_substr($string, $i, $split_length);
}

return
$array;
}
}
?>
0
efesar
13年前
这个简短的mb_trim函数对我有用。

<?php
function mb_trim( $string )
{
$string = preg_replace( "/(^\s+)|(\s+$)/us", "", $string );

return
$string;
}
?>
0
johannesponader at dontspamme dot googlemail dot co
14年前
请注意,在迁移代码以处理UTF-8编码时,不仅此处提到的函数有用,而且函数`htmlentities()`也必须更改为`htmlentities($var, ENT_COMPAT, "UTF-8")`或类似的。我没有查阅手册,但可能还有一些其他函数需要类似的调整。
0
marc at ermshaus dot org
16年前
对patrick at hexane dot org的`mb_str_replace`函数的一小处修正。如果`$replacement`包含`$needle`,则原始函数无法按预期工作。

<?php
function mb_str_replace($needle, $replacement, $haystack)
{
$needle_len = mb_strlen($needle);
$replacement_len = mb_strlen($replacement);
$pos = mb_strpos($haystack, $needle);
while (
$pos !== false)
{
$haystack = mb_substr($haystack, 0, $pos) . $replacement
. mb_substr($haystack, $pos + $needle_len);
$pos = mb_strpos($haystack, $needle, $pos + $replacement_len);
}
return
$haystack;
}
?>
0
patrick at hexane dot org
16年前
我想知道为什么没有`mb_str_replace()`函数。这里有一个临时的函数。

function mb_str_replace( $needle, $replacement, $haystack ) {
$needle_len = mb_strlen($needle);
$pos = mb_strpos( $haystack, $needle);
while (!($pos ===false)) {
$front = mb_substr( $haystack, 0, $pos );
$back = mb_substr( $haystack, $pos + $needle_len);
$haystack = $front.$replacement.$back;
$pos = mb_strpos( $haystack, $needle);
}
return $haystack;
}
0
chris at maedata dot com
17 年前
在导入/上传文件时,Eugene Murai之前评论中写的内容恰好相反。例如,如果使用“另存为Unicode文本”选项导出Excel电子表格,则可以在上传后使用以下方法将其转换为UTF-8。

//如果Windows弄乱了文件,则将其转换为UTF-8
$file = explode( "\n", mb_convert_encoding( trim( file_get_contents( $_FILES['file']['tmp_name'] ) ), 'UTF-8', 'UTF-16' ) );
0
pdezwart .at. snocap
18年前
如果您尝试模拟.NET中的`UnicodeEncoding.Unicode.GetBytes()`函数,则要使用的编码是:UCS-2LE。
0
daniel at softel dot jp
18年前
请注意,尽管“多字节”暗示了完全的国际化,但mb_ API是由日本人设计的,用于支持日语。

一些函数,例如`mb_convert_kana()`,在非日语环境中毫无意义。

如果这些函数能与非日语多字节语言一起工作,也许应该认为是“幸运的”。

我不是想对mb_ API表示任何不敬,因为我每天都在使用它,并且感谢它的实用性,但更好的名称可能是jp_ API。
0
Aardvark
18年前
由于并非所有托管服务目前都支持多字节函数集,因此可能仍然需要使用标准单字节函数来处理Unicode字符串。以下链接中的函数 - http://www.kanolife.com/escape/2006/03/php-unicode-processing.html - 通过示例说明了如何做到这一点。虽然这仅涵盖UTF-8,但标准PHP函数“iconv”允许在UTF-8和其他编码之间进行转换,如果需要输入或输出其他编码的字符串。
0
peter kehl
18年前
Eugene Murai针对Excel的UTF-16LE CSV解决方案运行良好。
$unicode_str_for_Excel = chr(255).chr(254).mb_convert_encoding( $utf8_str, 'UTF-16LE', 'UTF-8');

但是,Mac OS X上的Excel无法正确识别列,并且将其把每一整行都放在一个单元格中。为了解决这个问题,请使用制表符“\\t”字符作为CSV分隔符,而不是逗号或冒号。

您可能还想使用HTTP编码标头,例如
header( "Content-type: application/vnd.ms-excel; charset=UTF-16LE" );
0
Anonymous
19 年前
获取字符串的字节大小,当mbstring.func_overload设置为2时。

<?php
function str_sizeof($string) {
return
count(preg_split("`.`", $string)) - 1 ;
}
?>

回复peter albertsson,一旦你获得了数据的字节大小,你可以使用类似以下的方法访问每个字节
$string[0] ... $string[$size-1],因为`[`运算符不兼容多字节字符串。
-1
Daniel Rhodes
11年前
这是一个简单快捷的函数,用于从任何语言的 UTF-8 字符串中删除开头和结尾的*标点符号*(更具体地说,“非单词字符”)。(至少它对日语和英语都足够有效。)

/**
* 从字符串的开头和结尾修剪单字节和多字节标点符号
*
* @author Daniel Rhodes
* @note 我们希望第一个非单词抓取是贪婪的,但是
* @note 我们希望点星抓取(在最后一个非单词抓取之前)
* @note 是非贪婪的
*
* @param string $string UTF-8 编码的输入字符串
* @return string 与 $string 相同,但已删除开头和结尾的标点符号
*/
function mb_punctuation_trim($string)
{
preg_match('/^[^\w]{0,}(.*?)[^\w]{0,}$/iu', $string, $matches); //不区分大小写且非贪婪

if(count($matches) < 2)
{
//出现一些奇怪的错误,所以只返回原始输入
return $string;
}

return $matches[1];
}

希望您喜欢!
-1
hayk at mail dot ru
18年前
从PHP 5.1.0和PHP 4.4.2开始,可以使用亚美尼亚ArmSCII-8 (ArmSCII-8, ArmSCII8, ARMSCII-8, ARMSCII8)编码。
-2
peter dot albertsson at spray dot se
19 年前
设置`mbstring.func_overload = 2`可能会破坏处理二进制数据的应用程序。

在设置`mbstring.func_overload = 2`和`mbstring.internal_encoding = UTF-8`之后,我甚至无法读取二进制文件并将其打印/回显到输出而不会损坏它。
-2
mt at mediamedics dot nl
14年前
`str_split`函数(https://php.net/manual/en/function.str-split.php) 的多字节一对一替代方案

<?php
function mb_str_split($string, $split_length = 1){

mb_internal_encoding('UTF-8');
mb_regex_encoding('UTF-8');

$split_length = ($split_length <= 0) ? 1 : $split_length;

$mb_strlen = mb_strlen($string, 'utf-8');

$array = array();

for(
$i = 0; $i < $mb_strlen; $i += $split_length){

$array[] = mb_substr($string, $i, $split_length);
}

return
$array;

}
?>
-2
peter AT(no spam) dezzignz dot com
15年前
到目前为止,trim() 函数在我的多字节应用程序中一直没有让我失望,但如果有人需要一个真正多字节的函数,这里有一个。好消息是,要移除的字符可以是空格或任何其他指定的字符,甚至是多字节字符。

<?php

// 多字节字符串分割

function mbStringToArray ($str) {
if (empty(
$str)) return false;
$len = mb_strlen($str);
$array = array();
for (
$i = 0; $i < $len; $i++) {
$array[] = mb_substr($str, $i, 1);
}
return
$array;
}

// 移除两端$rem字符

function mb_trim ($str, $rem = ' ') {
if (empty(
$str)) return false;
// 转换为数组
$arr = mbStringToArray($str);
$len = count($arr);
// 左侧
for ($i = 0; $i < $len; $i++) {
if (
$arr[$i] === $rem) $arr[$i] = '';
else break;
}
// 右侧
for ($i = $len-1; $i >= 0; $i--) {
if (
$arr[$i] === $rem) $arr[$i] = '';
else break;
}
// 转换为字符串
return implode ('', $arr);
}

?>
-4
motin at demomusic dot nu
17 年前
正如 peter dot albertsson at spray dot se 指出的那样,重载 strlen 可能会破坏处理二进制数据并依赖 strlen 获取字节长度的代码。

问题发生在使用以下方式使用 fwrite 将字符串写入文件时

$len = strlen($data);
fwrite($fp, $data, $len);

fwrite 将字节数作为第三个参数,但 mb_strlen 返回字符串中的字符数。由于多字节字符的长度可能超过一个字节,这将导致 $data 的最后几个字符永远不会写入文件。

在调查了数小时 PEAR::Cache_Lite 为什么不起作用后,我发现了上述问题。

我尝试使用单字节函数,但它不起作用。无论如何,在这里发布它,以防它能帮助其他人。

/**
* PHP 单字节函数模拟(未成功)
*
* 用法:sb_string(functionname, arg1, arg2, etc);
* 例子:sb_string("strlen", "tuöéä"); 返回 8(应该……)
*/
function sb_string() {

$arguments = func_get_args();

$func_overloading = ini_get("mbstring.func_overload");

ini_set("mbstring.func_overload", 0);

$ret = call_user_func_array(array_shift($arguments), $arguments);

ini_set("mbstring.func_overload", $func_overloading);

return $ret;

}
To Top