解析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 相关文章推荐
生成sessionid和随机密码的例子
Oct 09 PHP
解析:使用php mongodb扩展时 需要注意的事项
Jun 18 PHP
基于PHP文件操作的详细诠释
Jun 21 PHP
php session劫持和防范的方法
Nov 12 PHP
PHP curl 获取响应的状态码的方法
Jan 13 PHP
VPS中使用LNMP安装WordPress教程
Dec 28 PHP
PHP中Array相关函数简介
Jul 03 PHP
php版微信公众平台开发之验证步骤实例详解
Sep 23 PHP
PHP自定义多进制的方法
Nov 03 PHP
解决php extension 加载顺序问题
Aug 16 PHP
Yii框架模拟组件调用注入示例
Nov 11 PHP
PHP正则表达式之RCEService回溯
Apr 11 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微信开发之二维码生成类
2015/06/26 PHP
Centos 6.5系统下编译安装PHP 7.0.13的方法
2016/12/19 PHP
PHP 断点续传实例详解
2017/11/11 PHP
jQuery插件 tabBox实现代码
2010/02/09 Javascript
一款Jquery 分页插件的改造方法(服务器端分页)
2011/07/11 Javascript
jQuery侧边栏随窗口滚动实现方法
2013/03/04 Javascript
使用jQuery validate 验证注册表单实例演示
2013/03/25 Javascript
简体中文转换繁体中文(实现代码)
2013/12/25 Javascript
Node.js的包详细介绍
2015/01/14 Javascript
JS+CSS实现自适应选项卡宽度的圆角滑动门效果
2015/09/15 Javascript
基于Jquery和html5的7款个性化地图插件
2015/11/17 Javascript
Bootstrap分页插件之Bootstrap Paginator实例详解
2016/10/15 Javascript
基于slideout.js实现移动端侧边栏滑动特效
2016/11/28 Javascript
JavaScript实现修改伪类样式
2017/11/27 Javascript
vue2.0项目实现路由跳转的方法详解
2018/06/21 Javascript
15个顶级开源JavaScript框架和库
2018/10/10 Javascript
JavaScript判断浏览器版本的方法
2019/11/03 Javascript
Angular 多模块项目构建过程
2020/02/13 Javascript
JavaScript设计模式之门面模式原理与实现方法分析
2020/03/09 Javascript
微信小程序onShareTimeline()实现分享朋友圈
2021/01/07 Javascript
python创建线程示例
2014/05/06 Python
Python脚本实现代码行数统计代码分享
2015/03/10 Python
python使用多线程不断刷新网页的方法
2015/03/31 Python
Python操作RabbitMQ服务器实现消息队列的路由功能
2016/06/29 Python
python Selenium实现付费音乐批量下载的实现方法
2019/01/24 Python
在Python运行时动态查看进程内部信息的方法
2019/02/22 Python
为什么你还不懂得怎么使用Python协程
2019/05/13 Python
pycharm专业版远程登录服务器的详细教程
2020/09/15 Python
迪士尼西班牙官方网上商店:ShopDisney西班牙
2020/02/02 全球购物
个人安全生产承诺书
2014/05/22 职场文书
2014党员民主评议个人总结
2014/09/10 职场文书
房屋财产继承协议书范本
2014/11/03 职场文书
幼儿园园长安全责任书
2015/05/08 职场文书
2015年骨干教师工作总结
2015/05/26 职场文书
2015年幼儿园师德师风建设工作总结
2015/10/23 职场文书
Python之matplotlib绘制饼图
2022/04/13 Python