解析strtr函数的效率问题


Posted in PHP onJune 26, 2013

最近经常要对字符串进行匹配和替换操作,之前一般使用str_replace或者preg_replace,据说strtr的效率不错,所以对比了一下:

$i = 0;
$t = microtime(true);
for(;$i<1000;$i++)
{
    $str = strtr(md5($i), $p2);
}
var_dump(microtime(true)-$t);    //0.085476875305176
$t = microtime(true);
for(;$i<2000;$i++)
{
    $str = preg_replace($p, '', md5($i));
}
var_dump(microtime(true)-$t);   //0.09863805770874

结果显示,strtr的效率比preg_replace高约15%左右。
趁着周末,查看了strtr的php源码:
PHP_FUNCTION(strtr)
{
        zval **str, **from, **to;
        int ac = ZEND_NUM_ARGS();
        //参数检查(zend_get_parameters_ex函数定义在zend_api.c文件中)
        if (ac < 2 || ac > 3 || zend_get_parameters_ex(ac, &str, &from, &to) == FAILURE) {
                WRONG_PARAM_COUNT;
        }
        //参数检查
        if (ac == 2 && Z_TYPE_PP(from) != IS_ARRAY) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "The second argument is not an array.");
                RETURN_FALSE;
        }
        convert_to_string_ex(str);
        /* shortcut for empty string */
        //宏Z_STRLEN_PP定义在zend_operators.h
        if (Z_STRLEN_PP(str) == 0) {
                RETURN_EMPTY_STRING();
        }
        if (ac == 2) {
                php_strtr_array(return_value, Z_STRVAL_PP(str), Z_STRLEN_PP(str), HASH_OF(*from));
        } else {
                convert_to_string_ex(from);
                convert_to_string_ex(to);
                ZVAL_STRINGL(return_value, Z_STRVAL_PP(str), Z_STRLEN_PP(str), 1);
                php_strtr(Z_STRVAL_P(return_value),
                                  Z_STRLEN_P(return_value),
                                  Z_STRVAL_PP(from),
                                  Z_STRVAL_PP(to),
                                  MIN(Z_STRLEN_PP(from),
                                  Z_STRLEN_PP(to)));
        }
}

先看看php_strtr函数:
//trlen是字符串str_from与str_to的长度的最小值
PHPAPI char *php_strtr(char *str, int len, char *str_from, char *str_to, int trlen)
{
        int i;
        unsigned char xlat[256]; //
        if ((trlen < 1) || (len < 1)) {
                return str;
        }
        //xlat的下标与值相等
        for (i = 0; i < 256; xlat[i] = i, i++);
        //把from到to字符串的每一个字符对应起来。例如:from="ab",to="cd",则会产生这样的对应'a'=>'c', 'b'=>'d'。
        for (i = 0; i < trlen; i++) {
                xlat[(unsigned char) str_from[i]] = str_to[i];
        }
        //替换(不过觉得这个函数的效率还有可以改进的地方,因为如果需要替换的字符只是占整个字符串很少的部分,这样就有大部分的赋值操作其实并没有什么意义,这样的情况下感觉先判断再赋值感觉会高效一点。有空测试一下)
        for (i = 0; i < len; i++) {
                str[i] = xlat[(unsigned char) str[i]];
        }
        return str;
}

可见,在处理strtr('abcdaaabcd', 'ab', 'efd')这样的操作时,应该是很高效的。
(注意:这个操作输出efcdeeefcd) 
再看看php_strtr_array:
static void php_strtr_array(zval *return_value, char *str, int slen, HashTable *hash)
{
        zval **entry;
        char  *string_key;
        uint   string_key_len;
        zval **trans;
        zval   ctmp;
        ulong num_key;
        int minlen = 128*1024;
        int maxlen = 0, pos, len, found;
        char *key;
        HashPosition hpos;
        smart_str result = {0};
        HashTable tmp_hash;
        //把替换数组从hash复制到tmp_hash,并记录下标字符串的最大和最小长度
        zend_hash_init(&tmp_hash, 0, NULL, NULL, 0);
        zend_hash_internal_pointer_reset_ex(hash, &hpos);
        while (zend_hash_get_current_data_ex(hash, (void **)&entry, &hpos) == SUCCESS) {
                switch (zend_hash_get_current_key_ex(hash, &string_key, &string_key_len, &num_key, 0, &hpos)) {
                        case HASH_KEY_IS_STRING:
                                len = string_key_len-1;
                                if (len < 1) {
                                        zend_hash_destroy(&tmp_hash);
                                        RETURN_FALSE;
                                }
                                zend_hash_add(&tmp_hash, string_key, string_key_len, entry, sizeof(zval*), NULL);
                                if (len > maxlen) {
                                        maxlen = len;
                                }
                                if (len < minlen) {
                                        minlen = len;
                                }
                                break;
                        //下标如果是整形的话会转换成字符串类型,例如:array(10=>'aa')转换成array('10'=>'aa')
                        case HASH_KEY_IS_LONG:
                                Z_TYPE(ctmp) = IS_LONG;
                                Z_LVAL(ctmp) = num_key;
                                convert_to_string(&ctmp);
                                len = Z_STRLEN(ctmp);
                                zend_hash_add(&tmp_hash, Z_STRVAL(ctmp), len+1, entry, sizeof(zval*), NULL);
                                zval_dtor(&ctmp);
                                if (len > maxlen) {
                                        maxlen = len;
                                }
                                if (len < minlen) {
                                        minlen = len;
                                }
                                break;
                }
                zend_hash_move_forward_ex(hash, &hpos);
        }
        key = emalloc(maxlen+1);
        pos = 0; 
        //从字符串的第一个字符开始循环匹配,pos记录当前查找的位置
        while (pos < slen) {
                //当前位置加上最大长度,如果大于字符串长度,则最大长度就需要改变
                if ((pos + maxlen) > slen) {
                        maxlen = slen - pos;
                }
                found = 0;
                memcpy(key, str+pos, maxlen);
                //从最大长度开始匹配,就是说对'abcd',若array('a'=>'e','ab'=>'f'),则会先把ab替换为f,而不是先把a换成e。
                for (len = maxlen; len >= minlen; len--) {
                        key[len] = 0;
                        //因为使用了hash表,所以这样的效率还是挺高的
                        if (zend_hash_find(&tmp_hash, key, len+1, (void**)&trans) == SUCCESS) {
                                char *tval;
                                int tlen;
                                zval tmp;
                                if (Z_TYPE_PP(trans) != IS_STRING) {
                                        tmp = **trans;
                                        zval_copy_ctor(&tmp);
                                        convert_to_string(&tmp);
                                        tval = Z_STRVAL(tmp);
                                        tlen = Z_STRLEN(tmp);
                                } else {
                                        tval = Z_STRVAL_PP(trans);
                                        tlen = Z_STRLEN_PP(trans);
                                }
                                //加入结果
                                smart_str_appendl(&result, tval, tlen);
                                //向前跳跃
                                pos += len;
                                found = 1;
                                if (Z_TYPE_PP(trans) != IS_STRING) {
                                        zval_dtor(&tmp);
                                }
                                break;
                        }
                }
                if (! found) {
                        smart_str_appendc(&result, str[pos++]);
                }
        }
        efree(key);
        zend_hash_destroy(&tmp_hash);
        smart_str_0(&result);
        RETVAL_STRINGL(result.c, result.len, 0);
}

PHP 相关文章推荐
php中数据的批量导入(csv文件)
Oct 09 PHP
PHP的类 功能齐全的发送邮件类
Oct 09 PHP
PHP采集腾讯微博的实现代码
Jan 19 PHP
PHP连接MySQL的2种方法小结以及防止乱码
Mar 11 PHP
PHP跨平台获取服务器IP地址自定义函数分享
Dec 29 PHP
PHP生成压缩文件实例
Feb 07 PHP
php邮箱地址正则表达式验证
Nov 13 PHP
Yii2.0预定义的别名功能小结
Jul 04 PHP
PHP常用正则表达式精选(推荐)
May 28 PHP
laravel 去掉index.php伪静态的操作方法
Oct 12 PHP
yii框架结合charjs统计上一年与当前年数据的方法示例
Apr 04 PHP
PHP的重载使用魔术方法代码实例详解
Feb 26 PHP
如何在smarty中增加类似foreach的功能自动加载数据
Jun 26 #PHP
编写Smarty插件在模板中直接加载数据的详细介绍
Jun 26 #PHP
比较strtr, str_replace和preg_replace三个函数的效率
Jun 26 #PHP
解析php扩展php_curl.dll不加载的解决方法
Jun 26 #PHP
php ci框架验证码实例分析
Jun 26 #PHP
解析php file_exists无效的解决办法
Jun 26 #PHP
使用HMAC-SHA1签名方法详解
Jun 26 #PHP
You might like
php中get_headers函数的作用及用法的详细介绍
2013/04/27 PHP
基于PHP常用字符串的总结(待续)
2013/06/07 PHP
php define的第二个参数使用方法
2013/11/04 PHP
phpexcel导入excel数据使用方法实例
2013/12/24 PHP
Laravel 5 学习笔记
2015/03/06 PHP
php采集神器cURL使用方法详解
2016/02/19 PHP
JavaScript 一道字符串分解的题目
2011/08/03 Javascript
js动画效果制件让图片组成动画代码分享
2014/01/14 Javascript
jQuery之ajax删除详解
2014/02/27 Javascript
Javascript实现多彩雪花从天降散落效果的方法
2015/02/02 Javascript
JavaScript中实现键值对应的字典与哈希表结构的示例
2016/06/12 Javascript
JS for循环中i++ 和 ++i的区别介绍
2016/07/20 Javascript
微信小程序 animation API详解及实例代码
2016/10/08 Javascript
使用ReactJS实现tab页切换、菜单栏切换、手风琴切换和进度条效果
2016/10/17 Javascript
form+iframe解决跨域上传文件的方法
2016/11/18 Javascript
javaScript基础详解
2017/01/19 Javascript
JS中Safari浏览器中的Date
2017/07/17 Javascript
React+react-dropzone+node.js实现图片上传的示例代码
2017/08/23 Javascript
webpack常用配置总览(小结)
2019/11/18 Javascript
python实现对一个完整url进行分割的方法
2015/04/29 Python
python中如何使用正则表达式的集合字符示例
2017/10/09 Python
Android基于TCP和URL协议的网络编程示例【附demo源码下载】
2018/01/23 Python
python 简单照相机调用系统摄像头实现方法 pygame
2018/08/03 Python
Python 3.8 新功能来一波(大部分人都不知道)
2020/03/11 Python
django model的update时auto_now不被更新的原因及解决方式
2020/04/01 Python
HTML5 实战PHP之Web页面表单设计
2011/10/09 HTML / CSS
Expedia丹麦:全球领先的旅游网站
2018/03/18 全球购物
高尔夫球鞋、服装、手套和装备:FootJoy
2018/12/15 全球购物
与世界上最好的跑步专业品牌合作:Fleet Feet
2019/03/22 全球购物
德国自然时尚和有机产品购物网站:Waschbär
2019/05/29 全球购物
移动通信行业实习自我鉴定
2013/09/28 职场文书
暑期培训班招生方案
2014/08/26 职场文书
党性教育心得体会(共6篇)
2016/01/21 职场文书
UNION CREATIVE《Re:从零开始的异世界生活》雷姆手办
2022/03/20 日漫
vue判断按钮是否可以点击
2022/04/09 Vue.js
Go Grpc Gateway兼容HTTP协议文档自动生成网关
2022/06/16 Golang