php中str_replace导致乱码的诡异问题

支付宝内搜索 9155838 即可领现金红包 每天都能领哦

10:19:17
朋友有个站的文章内容有时候会显示乱码,让我给看看。

拿到服务器权限后,我上去一看,php都几百个,觉得有点头大,感觉无从查起,并且我也不想在本地重新布置环境,要下载代码和数据库好麻烦。

就只能在线调试了,改了点东西就可能500了,坑爹的是在php.ini里面打开报错,重启后仍然看不到错误信息,在nginx日志和php日志里面也看不到错误信息,不知服务器配置是怎么设置的,但也不想去研究配置了 -_-||

凭着我三脚猫的功夫经过大半天的排查修改,将近凌晨的时候终于在几十个模块中将问题定位到str_replace函数上面了:
问题出在有一行str_replace是将全角空格替换为半角空格的代码(即下图第9行),将其注释即搞定。

现在将问题现象重新整理重现一下,将无关代码全部剔除,只提取最关键代码,又发现一个新的现象:原文本里面的“场。”经过替换后会变成“常”,这真是有点奇怪,如图:
php中str_replace导致乱码的问题

这时,没出现乱码的原因是我将mb_substr函数删掉了,因为这个时候乱码的问题已经不重要了,一个字变成另一个没替换过的字的问题才更诡异。

经测试任何版本的php都有此奇异现象

简要代码如下(php文件编码为gb2312):

<!DOCTYPE html><html><head><meta charset="gbk" />
<?php
header("Content-type:text/html; charset=gbk");
$arr1='这是心灵博客的一个测试用例,具体请见http://blog.dngz.net/phpstrreplace.htm,今天心灵博客发生了个开心的事,但你不在场。';
$arr1 = textReplace($arr1);
echo $arr1;

function textReplace($str)
{
    $str = str_replace(' ','',$str);  //屏蔽此行则正常
    $str = str_replace('开心','happy',$str);
    return $str;
}

php技术大佬帮我看看吧,但愿不是什么php对中文支持不够友好的说法……


2019-11-22 23:00:38 补充:
产生这个问题的原因:
将这几个字的编码转换一下:
场。 常
编码转码后:
%b3%a1 %a1%a3 %a1%a1 %b3%a3
“场”的后半部分和句号的前半部分正好组成全角的%a1%a1被替换为空,然后剩余部分正好组成%b3%a3,正好是“常”的编码。

感谢php大神自由勇给出的解决方法:

我有时连续几个月每天PHP编程10小时,很多时候都在处理这类问题。
这个问题PHP短期内无法处理,因为它是属于全角字符的编码的原因。以前写过7年的ASP程序,ASP、JavaScript就不存在这个问题,它们都是把全角字符认为是一个字符。而PHP在GBK/GB2312下,把全角字符拆分成2个字符,UTF-8编码下,拆分成3个字符。

如果要专项替换这个全角空格,还有一个办法,不使用str_replace,稍微有点复杂,但是这个方法(公式)在项目中会特别常用。
以GB2312编码为例,让循环程序检索整个字符串,进行累加。例如:
$a1是字符串:

<?php
$a1='这是心灵博客的一个测试用 例';
$j=strlen($a1)-1;$c1='';
for ($i=0;$i<=$j;$i++){$a=$a1[$i];if (ord($a)>126){$a=$a.$a1[$i+1];$i++;}
if ($a==' ') $a='';
$c1.=$a;
}

echo $c1;?>

运行结果:这是心灵博客的一个测试用例

完美替换掉了全角空格。

当出现全角字符时,ord的值会大于126时,说明此字符一定是全角汉字,此时$i++会向下跳过一个半角字符。

同理,如果是UTF-8编码,则改为:

if (ord($a)>126){$a=$a.$a1[$i+1].$a1[$i+2];$i+=2;}

推荐文章

已有 42 条评论
  1. 自由勇

    出现这个问题,的确是PHP对中文的支持有点不好,PHP的一个bug。这是因为PHP,全角字符是拆成2部分的编码。恰好有2个连续的字,第1个字的后半部分,和第2个字的前半部分,组成了如程序中的“ ”空格这个字符(屏蔽此行则正常)。

    测试中,如果把这行改为 $str = str_replace(' ','abc',$str); ,后面的结果是“砤bc”,证实了这一点。

    这个暂时没有办法解决,只能把 $str = str_replace(' ','',$str); 这一行删除。这样的话,一般会很少遇到这种bug。

    自由勇 回复
    1. xylx

      @自由勇

      高手,正好验证了我的想法。
      刚刚又看了下,将这几个字的编码转换一下:
      场。 常
      转码后:
      %b3%a1%a1%a3%a1%a1%b3%a3
      场的后半部分和句号的前半部分正好组成全角的%a1%a1,然后剩余部分正好组成%b3%a3的常。
      这样看来,在适当的时候str_replace很有可能导致非常多的异常问题。
      这样的问题提给php不知道会不会处理。

      xylx 回复
      1. 自由勇

        @xylx

        谢谢!我有时连续几个月每天PHP编程10小时,很多时候都在处理这类问题。
        这个问题PHP短期内无法处理,因为它是属于全角字符的编码的原因。以前写过7年的ASP程序,ASP、JavaScript就不存在这个问题,它们都是把全角字符认为是一个字符。而PHP在GBK/GB2312下,把全角字符拆分成2个字符,UTF-8编码下,拆分成3个字符。

        如果要专项替换这个全角空格,还有一个办法,不使用str_replace,稍微有点复杂,但是这个方法(公式)在项目中会特别常用。
        以GB2312编码为例,让循环程序检索整个字符串,进行累加。例如:
        $a1是字符串:

        $a1='这是心灵博客的一个测试用 例';
        $j=strlen($a1)-1;$c1='';
        for ($i=0;$i126){$a=$a.$a1[$i+1];$i++;}
        if ($a==' ') $a='';
        $c1.=$a;
        }

        echo $c1;

        运行结果:这是心灵博客的一个测试用例

        完美替换掉了全角空格。

        当出现全角字符时,ord的值会大于126时,说明此字符一定是全角汉字,此时$i++会向下跳过一个半角字符。

        同理,如果是UTF-8编码,则改为:

        if (ord($a)>126){$a=$a.$a1[$i+1].$a1[$i+2];$i+=2;}

        自由勇 回复
        1. 自由勇

          @自由勇

          上述程序被过滤掉了:

          $a1='这是心灵博客的一个测试用 例';
          $j=strlen($a1)-1;$c1='';
          for ($i=0;$i126){$a=$a.$a1[$i+1];$i++;}
          if ($a==' ') $a='';
          $c1.=$a;
          }
          echo $c1;

          运行结果:这是心灵博客的一个测试用例

          完美替换掉了全角空格。

          当出现全角字符时,ord的值会大于126时,说明此字符一定是全角汉字,此时$i++会向下跳过一个半角字符。

          自由勇 回复
        2. 自由勇

          @自由勇

          程序中还是有很多字符被过滤掉了,我把程序贴在了这里:

          http://www.auiou.com/1.htm

          自由勇 回复
          1. xylx

            @自由勇

            看到了,已保存,谢谢。

            xylx
  2. 自由勇

    或者替换的目的是为了删除文章中每段的2个全角空格,这一行改为双空格,就不会有乱码了:

    $str = str_replace('  ','',$str);

    自由勇 回复
  3. 后宫学长

    这肯定还在哪里埋了坑……

    后宫学长 回复
  4. 执迷不悟

    都是高手,我完全看不懂😕。不过,下意识看了下我的静态博客编码设置:“”。应该不会有类似的问题吧?

    执迷不悟 回复
    1. xylx

      @执迷不悟

      你的博客也折腾得挺好的啊

      xylx 回复
  5. 老杨

    涨知识了……

    老杨 回复
  6. 逆时针

    不懂技术的我,只能在一旁观看了~~

    逆时针 回复
  7. 龙笑天

    一些不容易注意到的地方 排查起来麻烦的很~ PS:贵站貌似换域名了?

    龙笑天 回复
    1. xylx

      @龙笑天

      十几年都是这个域名没变过

      xylx 回复
      1. 龙笑天

        @xylx

        0.0 那估计我记错了...

        龙笑天 回复
  8. 猫叔

    我什么也没看懂,但是还是要留个言!

    猫叔 回复
    1. xylx

      @猫叔

      猫叔叔,我的logo呢? 眼看就快过年了 o(╥﹏╥)o

      xylx 回复
      1. 猫叔

        @xylx

        呀呀呀!给忘记了,这咋办。。。。。。。。

        猫叔 回复
  9. 云中君

    除了感觉到复杂以外,啥也看不懂

    云中君 回复
    1. 云中君

      @云中君

      还好我弄的是静态博客,否则只能干瞪眼了

      云中君 回复
  10. 张波博客

    这么长时间没有更新了,我以为。。。

    张波博客 回复
  11. 石樱灯笼

    gb2312,感觉一下回到15年前

    石樱灯笼 回复
    1. xylx

      @石樱灯笼

      utf8并不是含着金钥匙出生的吧?编码没有优劣之分吧?不同的情况下用不同的编码是有它的优势的,小范围使用不用共享数据等情况下至少能省空间。

      xylx 回复
      1. 石樱灯笼

        @xylx

        “欢迎来到日本”

        石樱灯笼 回复
  12. 鸟叔

    鸟叔看的满头雾水,囧

    鸟叔 回复
  13. 灰狼

    看开头就在想,应该是编码的问题。建议统一使用utf8

    灰狼 回复
    1. xylx

      @灰狼

      不关编码的事

      xylx 回复
  14. 姜辰

    我感觉就是对中文的问题。。。emmmm

    不过一说到乱码,我第一时间居然会想到编码。emmmmmm

    开启报错这个,.ini文件,或者直接在文件头加开启报错呗。也许哪里直接屏蔽了报错。2333

    姜辰 回复
    1. xylx

      @姜辰

      ini里和nginx都修改了,但是还是不显示,挺奇怪的。

      xylx 回复
      1. 姜辰

        @xylx

        在php文件头添加一句:error_reporting(E_ALL);

        姜辰 回复
        1. xylx

          @姜辰

          试过哦,没用,他那个服务器安全配置做得太强了,一时找不出来哪里限制了。

          xylx 回复
  15. Escher

    精通PHP,羡慕啊

    Escher 回复
  16. 郑永

    考验技术的时候就是没有搜索引擎的时候是否能解决问题。。牛。有时候感觉就因为有了搜索引擎,很多技能都不想学习了。。。遇到问题,直接搜,有依耐性。

    郑永 回复
    1. xylx

      @郑永

      哈哈,我喜欢先自己折腾,折腾不出来再去搜索。

      xylx 回复
  17. 闲鱼

    能找到问题都是高手

    闲鱼 回复
    1. xylx

      @闲鱼

      高手是短时间内找到问题原因,我是瞎猫碰到死耗子,要花很久的时间。

      xylx 回复
  18. 毓彦

    虽然看不懂,但是不明觉厉

    毓彦 回复
  19. WordPress模板

    编码兼容世纪难题

    WordPress模板 回复
  20. Sam.Z

    学习了,这就是每个程序语言的不同处,很多方法处理不一样,虽然是一样的逻辑但是需要不同的方式,各种总有些许差别,又学到一招。

    Sam.Z 回复
    1. xylx

      @Sam.Z

      php处理中文太弱了

      xylx 回复
  21. Tsez

    没想到现在还有这么高产、历史这么悠久、人气也很高的个人博客!点个赞

    Tsez 回复
  22. 两对半

    被php的博大精深给吓到了

    两对半 回复
发表新评论