解析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批量删除数据
Jan 18 PHP
Zend framework处理一个http请求的流程分析
Feb 08 PHP
php数据库密码的找回的步骤
Jan 12 PHP
php笔记之:文章中图片处理的使用
Apr 26 PHP
PHP执行批量mysql语句的解决方法
May 02 PHP
yii2 modal弹窗之ActiveForm ajax表单异步验证
Jun 13 PHP
php获取目录中所有文件名及判断文件与目录的简单方法
Mar 04 PHP
laravel执行php artisan migrate报错的解决方法
Oct 09 PHP
php 使用expat方式解析xml文件操作示例
Nov 26 PHP
PHP设计模式之适配器模式(Adapter)原理与用法详解
Dec 12 PHP
tp5框架前台无限极导航菜单类实现方法分析
Mar 29 PHP
PHP SESSION跨页面传递失败解决方案
Dec 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 多维数组排序(usort,uasort)
2010/06/30 PHP
解析使用ThinkPHP应该掌握的调试手段
2013/06/20 PHP
php获取$_POST同名参数数组的实现介绍
2013/06/30 PHP
php实现获取文件mime类型的方法
2015/02/11 PHP
PHP Reflection API详解
2015/05/12 PHP
国外的为初学者写的JavaScript教程
2008/06/09 Javascript
让GoogleCode的SVN下的HTML文件在FireFox下正常显示.
2009/05/25 Javascript
JavaScript中通过闭包解决只能取得包含函数中任何变量最后一个值的问题
2010/08/12 Javascript
jQuery基本过滤选择器使用介绍
2013/04/18 Javascript
Javascript 拖拽雏形(逐行分析代码,让你轻松了拖拽的原理)
2015/01/23 Javascript
JavaScript函数详解
2015/02/27 Javascript
javascript结合CSS实现苹果开关按钮特效
2015/04/07 Javascript
JS实现光滑展开合拢的菜单效果代码
2015/09/16 Javascript
分享两款带遮罩的jQuery弹出框
2015/12/30 Javascript
jQuery插件实现文字无缝向上滚动效果代码
2016/02/25 Javascript
JavaScript控制输入框中只能输入中文、数字和英文的方法【基于正则实现】
2017/03/03 Javascript
zTree实现节点修改的实时刷新功能
2017/03/20 Javascript
纯jQuery实现前端分页功能
2017/03/23 jQuery
javascript中的面向对象
2017/03/30 Javascript
提高Node.js性能的应用技巧分享
2017/08/10 Javascript
利用pm2部署多个node.js项目的配置教程
2017/10/22 Javascript
Layer弹出层动态获取数据的方法
2018/08/20 Javascript
详解Vue组件插槽的使用以及调用组件内的方法
2018/11/13 Javascript
Vue Router 实现动态路由和常见问题及解决方法
2020/03/06 Javascript
详解Vue 数据更新了但页面没有更新的 7 种情况汇总及延伸总结
2020/05/28 Javascript
[02:02]2018DOTA2亚洲邀请赛Mineski赛前采访
2018/04/04 DOTA
Python通过90行代码搭建一个音乐搜索工具
2015/07/29 Python
Python 专题五 列表基础知识(二维list排序、获取下标和处理txt文本实例)
2017/03/20 Python
python使用folium库绘制地图点击框
2018/09/21 Python
python中class的定义及使用教程
2019/09/18 Python
Python使用Matlab命令过程解析
2020/06/04 Python
pycharm中如何自定义设置通过“ctrl+滚轮”进行放大和缩小实现方法
2020/09/16 Python
python 爬取腾讯视频评论的实现步骤
2021/02/18 Python
民族团结好少年事迹材料
2014/08/19 职场文书
个人作风建设总结
2014/10/23 职场文书
建党伟业的观后感
2015/06/01 职场文书