PHP中多字节字符串操作实例详解


Posted in PHP onAugust 23, 2021

前言

什么是多字节的字符串操作呢?其实不少的同学可能都已经使用过了,但我们还是要从最基础的问题说起。

一个字符占几个字节并不是我们表面上看到的那样。正常情况下,一个数字或英文以及英文符号都是占用一个字节的。但是这个世界的语言文字何其之多,特别是像中文、日文这样的文字,往往用一个字节装不下,这时候就需要多字节来解决了(多字节一般第一个字节是前导字节表示当前是什么语言文字,后面的是正被的字节编码)。比如说一个中文字在 GBK 环境是占用两个字节,而在 UTF-8 下则是占用三个字节。而在最近几年,由于 emoji 表情的出现 UTF-8MB4 又成为了主流,在表示这些 emoji 表情字符的时候,往往又会使用 UTF-8MB4 这种占用四个字节的编码格式来表示。

虽说字节的不同设置能够帮助我们展示丰富的内容,但对它的一些操作却也带来了麻烦。

字符串操作

$str = "abc测试一下";
echo strlen($str), PHP_EOL; // 15

strlen() 函数大家都不陌生,但是对于中文来说,它返回的数量明显是不对的。我们当前默认的编码格式是 UTF-8 ,所以将一个中文当做三个英文字符来数就正好是 15 个字符长度。很明显,这不是我们想要的结果,假设我们要截取字符串的话,这个长度的计算可是很费劲的,搞不好还容易出现乱码。

幸好在 PHP 的默认扩展中就已经为我们准备好了一组 mb_ 函数库,专门用来处理这类多字节字符串的问题。

echo mb_strlen($str), PHP_EOL; // 7
echo mb_strlen($str, 'GB2312'), PHP_EOL; // 11

在不指定 mb_strlen() 函数的第二个参数的情况下,会按照当前文档的默认编码格式来进行转换,所以我们的字符串长度就在 UTF-8 的环境下正常显示了。当然,我们也可以指定第二个参数为其它的编码格式,比如以前常用的 GB2312 或者 GBK ,这样返回的字符长度就是以一个中文占两个字节的形式返回长度了。

var_dump(mb_strpos($str, "测")); // int(3)

var_dump(mb_convert_case($str, MB_CASE_UPPER)); // string(15) "ABC测试一下"
var_dump(mb_convert_case($str, MB_CASE_LOWER)); // string(15) "abc测试一下"

var_dump(mb_substr($str, 5)); // string(6) "一下"

当然,mb_ 相关的字符串操作函数是比较全面的,字符出现位置、大小写转换、截取字符串等函数都是提供的,调用的参数也都和普通的字符串操作函数没什么区别,只是它们多了一个可选的指定编码的参数。在通常的情况下,只要我们的文件是对应的编码格式,这个参数就不用去写了。

当然,字符串的操作函数还有很多,这里就不一一列举了,大家可以自行查阅相关的文档。

字符串正则操作

既然说到了字符串的操作,正则相关的功能也是必不可少的,我们先看下使用默认的 preg_ 相关的函数操作中文的问题。

$str = iconv('UTF-8', 'GB2312', $str);

var_dump(preg_match("/[a-z]*测试/i", $str)); // int(0)
var_dump(preg_replace("/[a-z]*测试/i","试试", $str)); // string(11) "abc����һ��"

首先我们将测试用的字符串转换为 GB2312 的形式。就像我们获取的外部接口可能返回的就是 GB2312 的编码的。这时直接使用 preg_ 相关的函数是无法正确获得我们想要的结果的。

mb_regex_encoding('GB2312');
$pattern = iconv('UTF-8', 'GB2312', "[a-z]*测试");
var_dump(mb_ereg($pattern, $str)); // int(1)
var_dump(mb_eregi($pattern, $str)); // int(1)

var_dump(mb_ereg_replace($pattern,"试试", $str)); // string(10) "试试һ��"
var_dump(mb_eregi_replace($pattern,"试试", $str)); // string(10) "试试һ��"

接下来我们通过 mb_ereg 相关的函数来进行正则的匹配和替换,就能正常的对不同编码的字符串进行操作了。注意,我们需要指定 mb_regex_encoding() 函数,告诉当前默认的规划替换编码是 GB2312 ,同时,正则规则也要转换成对应的编码格式。

mb_eregi 相关的函数和 mb_ereg 其实没有本质上的区别,只是它不区分大小写了,就像 preg 相关函数中我们写正则时的后缀符号 i 一样。ereg 相关的函数都是不用写反斜杠的,在普通的函数中其实是已经被淘汰了的函数(性能没有 preg 好,语法也有区别),大部分情况下都会直接使用 preg 相关的函数来进行操作。不过如果是牵涉到多字节相关的问题,在 mb_ 函数库中还是只有 ereg 这类的函数可以使用。

字符串编码转换

就像我们之前学习过的 iconv() 函数一样,mb_ 库中也提供了字符编码转换的函数。

$phone = file_get_contents('https://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=13888888888');

print_r($phone);
// __GetZoneResult_ = {
//     mts:'1388888',
//     province:'����',
//     catName:'�й��ƶ�',
//     telString:'13888888888',
// 	areaVid:'30515',
// 	ispVid:'3236139',
// 	carrier:'�����ƶ�'
// }

var_dump(mb_convert_encoding($phone, 'UTF-8', "GBK"));
// string(183) "__GetZoneResult_ = {
//     mts:'1388888',
//     province:'云南',
//     catName:'中国移动',
//     telString:'13888888888',
// 	areaVid:'30515',
// 	ispVid:'3236139',
// 	carrier:'云南移动'
// }
// "

echo mb_detect_encoding($phone, 'UTF-8,GBK'), PHP_EOL; // CP936

同样我们还是拿这个获取手机号信息的公共接口测试,它返回的内容是 GBK 的编码内容。我们可以通过 mb_convert_encoding() 来转换它的编码内容。mb_detect_encoding() 是检测编码格式,这里我们给了两个参数,它会返回符合条件的编码内容,CP936 就是 GBK 的另一种表示(IBM在制作 code page 时将 GBK 编码放在了第 936 页)。

HTTP 参数操作

mb_internal_encoding("UTF-8");

首先介绍一个 mb_internal_encoding() 函数,其实就是设置当前运行环境中的默认编码规则的,如果不设置的话,就是以当前这个 php 文件的编码规则为默认的。大家了解一下,因为它会影响我们后面介绍的内容。

// // localhost:9991/?a=我上
var_dump(mb_http_input('GPC')); // bool(false)
var_dump(mb_http_output()); // string(5) "UTF-8"

mb_internal_encoding("CP936");
mb_parse_str($_SERVER['QUERY_STRING'], $result);
print_r($result);
// Array
// (
//     [a] => 我上
// )

首先我们运行起来测试文件,然后用浏览器请求这个链接地址。mb_http_input() 是检测 HTTP 输入字符编码,不过我测试的结果都是返回 false 。有了解的小伙伴可以留言说明下这个是什么情况。而 mb_http_output 则是设置检测输出的编码,这个就会受到 mb_internal_encoding() 所定义的内容的影响。

另外,mb_parse_str() 是 parse_str() 函数的多字节版,我们可以将浏览器的默认编码转换成 GBK 或者 之后再来请求,因为我们设置当前的 mb_internal_encoding() 为 CP936 了。在默认情况下,如果使用 UTF-8 的浏览器请求的话,这里就会报错了,这就是 mb_internal_encoding() 对这些函数的影响。

其它属性查看

最后,我们再来看看一些 mb_ 相关信息属性的内容。

var_dump(mb_language());
// string(7) "neutral"

mb_language() 函数用于获取/设置当前的语言,它可以接收一个参数设置当前的语言信息。主要用于编码邮件信息  mb_send_mail() 函数就是使用它来对邮件进行编码。关于  mb_send_mail() 的使用大家可以自己尝试一下,其实也是 send_mail() 函数的多字节版。neutral 的意思是中立的,其实也是跟我们的 mb_internal_encoding() 有关。

var_dump(mb_list_encodings());
// array(86) {
//     [0]=>
//     string(4) "pass"
//     [1]=>
//     string(5) "wchar"
//     [2]=>
//     string(7) "byte2be"
//     [3]=>
//     ……
//     [65]=>
//     string(5) "CP936"
//     ……

mb_list_encodings() 用于展示当前系统中所支持的所有语言编码的列表,在这个列表中我们就可以看到 CP936 的身影,但是没有 GBK 哦,记住它们俩是一个东西就好了。

var_dump(mb_get_info());
// array(14) {
//     ["internal_encoding"]=>
//     string(5) "UTF-8"
//     ["http_output"]=>
//     string(5) "UTF-8"
//     ["http_output_conv_mimetypes"]=>
//     string(31) "^(text/|application/xhtml\+xml)"
//     ["func_overload"]=>
//     int(0)
//     ["func_overload_list"]=>
//     string(11) "no overload"
//     ["mail_charset"]=>
//     string(5) "UTF-8"
//     ["mail_header_encoding"]=>
//     string(6) "BASE64"
//     ["mail_body_encoding"]=>
//     string(6) "BASE64"
//     ["illegal_chars"]=>
//     int(0)
//     ["encoding_translation"]=>
//     string(3) "Off"
//     ["language"]=>
//     string(7) "neutral"
//     ["detect_order"]=>
//     array(2) {
//       [0]=>
//       string(5) "ASCII"
//       [1]=>
//       string(5) "UTF-8"
//     }
//     ["substitute_character"]=>
//     int(63)
//     ["strict_detection"]=>
//     string(3) "Off"
//   }

mb_get_info() 是查看当前环境下默认的这些语言编码的配置,比如我们熟悉的 internal_encoding 、 http_output 属性都能在这里看到。

总结

用过的同学是不是也发现了今天文章的新姿势了呢?没错,GBK 和 CP936 反而成为了今天文章的意外惊喜。这个在之前确实还真没有注意到。其实 mb_ 相关的函数的使用已经非常普遍了,基本算是学习 PHP 的入门必备知识了。它还有很多的函数并没有一一地列举出来,有兴趣的同学可以多多查阅官方手册进行更加深入地学习。

测试代码:

[github.com/zhangyue050…]

参考文档:

www.php.net/manual/zh/b

到此这篇关于PHP中多字节字符串操作的文章就介绍到这了,更多相关PHP多字节字符串操作内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

PHP 相关文章推荐
杏林同学录(三)
Oct 09 PHP
手把手教你使用DedeCms V3的在线采集图文教程
Apr 03 PHP
PHP iconv 函数转gb2312的bug解决方法
Oct 11 PHP
win7 64位系统 配置php最新版开发环境(php+Apache+mysql)
Aug 15 PHP
PHP面向对象程序设计之接口用法
Aug 20 PHP
php绘制一条弧线的方法
Jan 24 PHP
php curl 上传文件代码实例
Apr 27 PHP
PHP模板引擎Smarty内建函数section,sectionelse用法详解
Apr 11 PHP
php简单实现多语言切换的方法
May 09 PHP
Laravel5中防止XSS跨站攻击的方法
Oct 10 PHP
如何修改yii2.0自带的user表为其它的表
Aug 01 PHP
PHP children()函数讲解
Feb 03 PHP
PHP使用QR Code生成二维码实例
Jul 07 #PHP
PHP设计模式(观察者模式)
Jul 07 #PHP
eval(cmd)与eval($cmd)的区别与联系
用php如何解决大文件分片上传问题
Jul 07 #PHP
php 文件上传至OSS及删除远程阿里云OSS文件
Jul 04 #PHP
PHP实现两种排课方式
Linux系统下安装PHP7.3版本
You might like
浏览器关闭后,能继续执行的php函数(ignore_user_abort)
2012/08/01 PHP
php获取ip的三个属性区别介绍(HTTP_X_FORWARDED_FOR,HTTP_VIA,REMOTE_ADDR)
2012/09/23 PHP
PHP代码保护--Zend Guard的使用详解
2013/06/03 PHP
php实现的三个常用加密解密功能函数示例
2017/11/06 PHP
PHP自定义函数实现assign()数组分配到模板及extract()变量分配到模板功能示例
2018/05/23 PHP
php+js实现裁剪任意形状图片
2018/10/31 PHP
JavaScript 构造函数 面相对象学习必备知识
2010/06/09 Javascript
基于JQuery的简单实现折叠菜单代码
2010/09/15 Javascript
jQuery:节点(插入,复制,替换,删除)操作
2013/03/04 Javascript
javascript 三种方法实现获得和设置以及移除元素属性
2013/03/20 Javascript
JS控件ASP.NET的treeview控件全选或者取消(示例代码)
2013/12/16 Javascript
js实现界面向原生界面发消息并跳转功能
2016/11/22 Javascript
简单易懂的天气插件(代码分享)
2017/02/04 Javascript
jQuery md5加密插件jQuery.md5.js用法示例
2018/08/24 jQuery
微信小程序局部刷新触发整页刷新效果的实现代码
2018/11/21 Javascript
微信小程序下拉框组件使用方法详解
2018/12/28 Javascript
vue双向绑定数据限制长度的方法
2019/11/04 Javascript
vue 基于abstract 路由模式 实现页面内嵌的示例代码
2020/12/14 Vue.js
[42:11]TNC vs Pain 2018国际邀请赛小组赛BO2 第二场 8.17
2018/08/20 DOTA
Python中set与frozenset方法和区别详解
2016/05/23 Python
遗传算法python版
2018/03/19 Python
详解Python最长公共子串和最长公共子序列的实现
2018/07/07 Python
Django页面数据的缓存与使用的具体方法
2019/04/23 Python
Python3.5模块的定义、导入、优化操作图文详解
2019/04/27 Python
Win10环境python3.7安装dlib模块趟过的坑
2019/08/01 Python
django 通过url实现简单的权限控制的例子
2019/08/16 Python
突袭HTML5之Javascript API扩展1—Web Worker异步执行及相关概述
2013/01/31 HTML / CSS
Reebonz中国官网:新加坡奢侈品购物网站
2017/03/17 全球购物
附答案的Java面试题
2012/11/19 面试题
教堂婚礼主持词
2014/03/14 职场文书
基层党员公开承诺书
2014/05/29 职场文书
综治维稳工作承诺书
2014/08/30 职场文书
作风建设年度心得体会
2014/10/29 职场文书
2015年财务经理工作总结
2015/05/13 职场文书
小学体育队列队形教学反思
2016/02/16 职场文书
德生TECSUN S-2000使用手册文字版
2022/05/10 无线电