专栏名称: SegmentFault思否
SegmentFault (www.sf.gg)开发者社区,是中国年轻开发者喜爱的极客社区,我们为开发者提供最纯粹的技术交流和分享平台。
目录
相关文章推荐
码农翻身  ·  中国的大模型怎么突然间就领先了? ·  18 小时前  
程序员的那些事  ·  清华大学:DeepSeek + ... ·  3 天前  
程序员小灰  ·  3个令人惊艳的DeepSeek项目,诞生了! ·  2 天前  
OSC开源社区  ·  2024: 大模型背景下知识图谱的理性回归 ·  4 天前  
程序员的那些事  ·  成人玩偶 + ... ·  5 天前  
51好读  ›  专栏  ›  SegmentFault思否

PHP 源码探秘——为什么 trim 会导致乱码

SegmentFault思否  · 公众号  · 程序员  · 2018-01-31 08:00

正文

我的博客:https://mengkang.net/1039.html

运行以下代码:

  1. $tag = "互联网产品、";

  2. $text = rtrim($tag, "、");

  3. print_r($text);

我们可能以为会得到的结果是 互联网产品 ,实际结果是 互联网产� 。为什么会这样呢?

科普

PHP 里使用 mb_ 前缀的都是多字节函数:http://php.net/manual/zh/ref.mbstring.php。

比如:

  1. $str = "abcd";

  2. print_r(strlen($str)."\n"); // 4

  3. print_r(mb_strlen($str)."\n"); // 4

  4. $str = "周梦康";

  5. print_r(strlen($str)."\n"); // 9

  6. print_r(mb_strlen($str)."\n"); // 3

mb_ 系列函数是以“多个字节组成的一个字符”为颗粒度来操作的,不带 mb_ 则是按实际的字节数来操作的。

原理

trim 函数文档:

  1. string trim ( string $str [, string $character_mask = " \t\n\r\0\x0B" ] )

该函数不是多字节函数,也就是说,汉字这样的多字节字符,会拿其头或尾的单字节来和后面的 $character_mask 对应的char数组进行匹配,如果在后面的数组中,则删掉,继续匹配。比如:

  1. echo ltrim( "bcdf","abc"); // df

如下面的 demo 中的函数 string_print_char 所示:

  • 0xe30x800x81 三字节组成,

  • 0xe50x930x81 三字节组成。

所以在执行 rtrim 的时候,通过字节比对,会将 0x81 去掉,导致了最后出现了乱码。

源码探究

查看 PHP7 的源码,然后提炼出下面的小 demo ,方便大家一起学习,其实PHP源码的学习并不难,每天进步一点点。

  1. //

  2. //  main.c

  3. //  trim

  4. //

  5. //  Created by 周梦康 on 2017/10/18.

  6. //  Copyright © 2017年 周梦康. All rights reserved.

  7. //

  8. #include

  9. #include

  10. #include

  11. void string_print_char(char *str);

  12. void php_charmask(unsigned char *input, size_t len, char *mask);

  13. char *ltrim(char *str,char *character_mask);

  14. char *rtrim(char *str,char *character_mask);

  15. int main(int argc, char const *argv[])

  16. {

  17.    printf("%s\n",ltrim("bcdf","abc"));

  18.    string_print_char("品"); // e5    93  81

  19.    string_print_char("、"); // e3    80  81

  20.    printf("%s\n",rtrim("互联网产品、","、"));

  21.    return 0;

  22. }

  23. char *ltrim(char *str,char *character_mask)

  24. {

  25.    char *res;

  26.     char mask[256];

  27.    register size_t i;

  28.    int trimmed = 0;

  29.    size_t len = strlen(str);

  30.    php_charmask((unsigned char*)character_mask, strlen(character_mask), mask);

  31.    for (i = 0; i < len; i++) {

  32.        if (mask[(unsigned char)str[i]]) {

  33.            trimmed++;

  34.        } else {

  35.            break;

  36.        }

  37.    }

  38.    len -= trimmed;

  39.    str += trimmed;

  40.    res = (char *) malloc(sizeof(char) * (len+1));

  41.    memcpy(res,str,len);

  42.    return res;

  43. }

  44. char *rtrim(char *str,char *character_mask)

  45. {

  46.    char *res;

  47.    char mask[256];

  48.    register size_t i;

  49.    size_t len = strlen(str);

  50.    php_charmask((unsigned char*)character_mask, strlen(character_mask), mask);

  51.    if (len > 0) {

  52.        i = len - 1;

  53.        do {

  54.            if (mask[(unsigned char)str[i]]) {

  55.                len--;

  56.            } else {

  57.                break;

  58.            }

  59.        } while (i-- != 0);

  60.    }

  61.    res = (char *) malloc(sizeof(char) * (len+1));

  62.    memcpy(res,str,len);

  63.    return res;

  64. }

  65. void string_print_char(char *str)

  66. {

  67.    unsigned long l = strlen(str);

  68.    for (int i=0; i < l; i++) {

  69.        printf("%02hhx\t",str[i]);

  70.    }

  71.    printf("\n");

  72. }

  73. void php_charmask(unsigned char *input, size_t len, char *mask)

  74. {

  75.    unsigned char *end;

  76.    unsigned char c;

  77.    memset(mask, 0, 256);

  78.    for (end = input+len; input < end; input++) {

  79.        c = *input;

  80.        mask[c]= 1;

  81.    }

  82. }

如果觉得 demo 还不够清晰的,复制下来,自己执行一次吧~

C 语言基础较差的同学也不用担心,我准备后面专门写一个PHP小白学习 C 语言的系列入门短文哈。

解决方案

那么我们就依葫芦画瓢,用 php 本身的多字节函数来实现下吧:

  1. function mb_rtrim($string, $trim, $encoding)

  2. {

  3.    $mask = [];

  4.    $trimLength = mb_strlen($trim, $encoding);

  5.    for ($i = 0; $i < $trimLength; $i++) {

  6.        $item = mb_substr($trim, $i, 1, $encoding);

  7.        $mask[] = $item;

  8.    }

  9.    $len = mb_strlen($string, $encoding);

  10.    if ($len > 0) {

  11.        $i = $len - 1;

  12.        do {

  13.            $item = mb_substr($string, $i, 1, $encoding);

  14.            if (in_array($item, $mask)) {

  15.                $len--;

  16.            } else {

  17.                break;

  18.            }

  19.        } while ($i-- != 0);

  20.    }

  21.    return mb_substr($string, 0, $len, $encoding);

  22. }

  23. mb_internal_encoding("UTF-8");

  24. $tag = "互联网产品、";

  25. $encoding = mb_internal_encoding();

  26. print_r(mb_rtrim($tag, "、",$encoding));

当然你也可以使用正则来做。通过上面的函数学习,单字节函数和多字节函数,你学会了吗?

PHP7 相关源码

  1. PHP_FUNCTION(trim)

  2. {

  3.    php_do_trim(INTERNAL_FUNCTION_PARAM_PASSTHRU, 3);

  4. }

  5. PHP_FUNCTION(rtrim)

  6. {

  7.    php_do_trim(INTERNAL_FUNCTION_PARAM_PASSTHRU, 2);

  8. }

  9. PHP_FUNCTION(ltrim)

  10. {

  11.    php_do_trim(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);

  12. }

  1. static void php_do_trim(INTERNAL_FUNCTION_PARAMETERS, int mode)

  2. {

  3.    zend_string *str;

  4.    zend_string *what = NULL;

  5.    ZEND_PARSE_PARAMETERS_START(1, 2)

  6.        Z_PARAM_STR(str)

  7.        Z_PARAM_OPTIONAL

  8.        Z_PARAM_STR(what)

  9.    ZEND_PARSE_PARAMETERS_END();

  10.    ZVAL_STR(return_value, php_trim(str, (what ? ZSTR_VAL(what) : NULL), (what ? ZSTR_LEN(what) : 0), mode));

  11. }

  1. PHPAPI zend_string *php_trim(zend_string *str, char *what, size_t what_len, int mode)

  2. {

  3.    const char *c = ZSTR_VAL(str);

  4.    size_t len = ZSTR_LEN(str);

  5.    register size_t i;

  6.    size_t trimmed = 0;

  7.    char mask[256];

  8.    if (what) {

  9.        if (what_len == 1) {

  10.            char p = *what;

  11.            if (mode & 1) {

  12.                for (i = 0; i < len; i++) {

  13.                    if (c[i] == p) {

  14.                        trimmed++;

  15.                    } else {

  16.                        break;

  17.                    }

  18.                }

  19.                len -= trimmed;

  20.                c += trimmed;

  21.            }

  22.            if (mode & 2) {

  23.                if (len > 0) {

  24.                    i = len - 1;

  25.                    do {

  26.                        if (c[i] == p) {

  27.                            len--;

  28.                        } else {

  29.                            break;

  30.                        }

  31.                    } while (i-- != 0);

  32.                }

  33.            }

  34.        } else {

  35.            php_charmask((unsigned char*)what, what_len, mask);

  36.            if (mode & 1) {

  37.                for (i = 0; i < len; i++) {

  38.                    if (mask[(unsigned char)c[i]]) {

  39.                        trimmed++;

  40.                    } else {

  41.                        break;

  42.                    }

  43.                }

  44.                len -= trimmed;

  45.                c += trimmed;

  46.            }

  47.            if (mode & 2) {

  48.                if (len > 0) {

  49.                    i = len - 1;

  50.                    do {

  51.                        if (mask[(unsigned char)c[i]]) {

  52.                            len--;

  53.                        } else {

  54.                            break;

  55.                        }

  56.                    } while (i-- != 0);

  57.                }

  58.            }

  59.        }

  60.    } else {

  61.        if (mode & 1) {

  62.            for (i = 0; i < len; i++) {

  63.                if ((unsigned char)c[i] <= ' ' &&

  64.                    (c[i] == ' ' || c[i] == '\n' || c[i] == '\r' || c[i] == '\t' || c[i] == '\v' || c[i] == '\0')) {

  65.                    trimmed++;







请到「今天看啥」查看全文