多字节字符串函数

参考

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

目录

添加注释

用户贡献的注释 35 个注释

69
deceze at gmail dot com
11 年前
请注意,所有关于 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,但这与微软的意思略有不同:当微软说“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
匿名
10 年前
另一个单行 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
"Text length: ", mb_strlen($string), "\tString length: ", strlen($string), " ... ", $string, "\n";
// 三个字符,六个字节;文本读作“x<y”。

$newstring = str_replace('l', 'g', $string);
echo
"Text length: ", mb_strlen($newstring), "\tString length: ", strlen($newstring), " ... ", $newstring, "\n";
// 三个字符,六个字节,但现在文本读作“x>y”;错误的字符已更改。

$newstring = str_replace(';', ':', $string);
echo
"Text length: ", mb_strlen($newstring), "\tString length: ", strlen($newstring), " ... ", $newstring, "\n";
// 现在连文本的长度都错了,文本也被破坏了。

?>

即使“x<y”文本中没有出现 'l' 或 ';',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
14 年前
这将是创建多字节 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
15 年前
PHP5 没有 mb_trim(),所以这里我创建了一个。它与 trim() 的工作方式相同,但额外增加了 PCRE 字符类(当然,包括所有有用的 Unicode 字符类,例如 \pZ)。

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

<?php
/**
* 从字符串的两端(或两端)修剪字符,以多字节友好的方式。
*
* 大多数情况下,它的行为与 trim() 完全相同:例如,提供 'abc' 作为
* 字符列表将从字符串中修剪所有 'a'、'b' 和 'c' 字符,当然,
* 还增加了在字符列表中添加 Unicode 字符的额外好处。
*
* 我们使用 PCRE 字符类以 Unicode 敏感的方式进行修剪,因此我们必须转义 ^、\、- 和 ],它们在这里具有特殊含义。
* 正如您所期望的那样,字符列表中的单个 \ 被解释为
* “修剪反斜杠”(并相应地转义为双 \)。在大多数情况下
* 您可以忽略此细节。
*
* 但是,作为一项额外功能,我们还允许 PCRE 特殊字符类(例如 '\s')
* 因为它们在处理 UCS 时非常有用。例如,'\pZ',
* 匹配 Unicode 中定义的每个“分隔符”字符,包括不间断
* 和零宽度空格。
*
* 在字符中包含两个或多个相同字符是没有意义的
* 类,因此我们将字符列表中的双 \ 解释为
* 正则表达式中的单个 \,允许您安全地混合普通字符和 PCRE
* 特殊类。
*
* *注意* 在使用此附加功能时,因为 PHP 也会解释反斜杠
* 作为转义字符,甚至在它们被正则表达式看到之前。因此,到
* 在正则表达式中指定 '\\s'(它将转换为特殊字符
* 类 '\s' 用于修剪),您通常需要在 *4* 个反斜杠中放置 *4* 个反斜杠
* PHP 代码 - 正如您从 $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 上的 php
7 年前
str_replace 不是多字节安全的。

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

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

$result = str_replace(str_split($rubishcharacters), ' ', $searchstring);
2
gmail.com 上的 abidul.rmdn
5 年前
如果你有一个大型项目,迁移到 MB 函数可能有点痛苦。在我们公司花了一段时间,但后来我们编写了一个小脚本并在一个小博客中解释了它。
https://link.medium.com/25w1LronCX

这使得迁移到 mb_ 函数变得非常容易。
2
Daniel Rhodes
10 年前
这是一个廉价且厚脸皮的函数,用于从 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 上的 sakai
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 上的 rawsrc
13 年前
嗨,

对于那些正在寻找 mb_str_replace 的人,这里有一个简单的函数
<?php
function mb_str_replace($needle, $replacement, $haystack) {
return
implode($replacement, mb_split($needle, $haystack));
}
?>
我还没有找到最简单的方法来继续 :-)
1
NOSPAMmte.biglobe.ne.jp 上的 nzkiwi
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 上的 v.r.sanaty
6 年前
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
10 年前
这是一个廉价且厚脸皮的函数,用于从 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 上的 rr_news
7 年前
"mediamedics.nl 上的 mt" 的建议并不像否决票所显示的那样糟糕。只有一个小的错误,可以很容易地修复以使其工作。
需要修改 "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
13 年前
请注意,在将代码迁移到处理 UTF-8 编码时,不仅提到的这些函数很有用,而且函数 htmlentities() 也需要更改为 htmlentities($var, ENT_COMPAT, "UTF-8") 或类似方法。我没有查看手册,但可能还有一些函数需要像这样进行调整。
0
marc at ermshaus dot org
15 年前
对 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
17 年前
如果您试图在 .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 无法正确识别列,并将整行都放在一个单元格中。为了解决这个问题,请使用 TAB "\\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
10 年前
这是一个廉价且厚脸皮的函数,用于从 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
17 年前
从 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
14 年前
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