解析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 相关文章推荐
用Zend Encode编写开发PHP程序
Oct 09 PHP
生成sessionid和随机密码的例子
Oct 09 PHP
PHP通用分页类page.php[仿google分页]
Aug 31 PHP
PHP的基本常识小结
Jul 05 PHP
PHP实现QQ登录实例代码
Jan 14 PHP
php版微信公众平台之微信网页登陆授权示例
Sep 23 PHP
PHP封装的多文件上传类实例与用法详解
Feb 07 PHP
phpstudy默认不支持64位php的解决方法
Feb 20 PHP
php实现的二叉树遍历算法示例
Jun 15 PHP
搜索附近的人PHP实现代码
Feb 11 PHP
PHP设计模式之简单工厂和工厂模式实例分析
Mar 25 PHP
Laravel 创建可以传递参数 Console服务的例子
Oct 14 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中动态HTML的输出技术
2006/10/09 PHP
php 大数据量及海量数据处理算法总结
2011/05/07 PHP
php mysql 判断update之后是否更新了的方法
2012/01/10 PHP
使用php 获取时间今天明天昨天时间戳的详解
2013/06/20 PHP
php生成EAN_13标准条形码实例
2013/11/13 PHP
详解WordPress中用于更新和获取用户选项数据的PHP函数
2016/03/08 PHP
JS date对象的减法处理实现代码
2010/12/28 Javascript
Js实现当前点击a标签变色突出显示其他a标签回复原色
2013/11/27 Javascript
用jquery实现动画跳到顶部和底部(这个比较简单)
2014/09/01 Javascript
最简单的JavaScript图片轮播代码(两种方法)
2015/12/18 Javascript
ajax图片上传,图片异步上传,更新实例
2016/12/30 Javascript
利用Vue.js实现checkbox的全选反选效果
2017/01/18 Javascript
nodejs利用ajax实现网页无刷新上传图片实例代码
2017/06/06 NodeJs
微信小程序简单实现form表单获取输入数据功能示例
2017/11/30 Javascript
深入理解requireJS-实现一个简单的模块加载器
2018/01/15 Javascript
深入理解vue中slot与slot-scope的具体使用
2018/01/26 Javascript
mint-ui在vue中的使用示例
2018/04/05 Javascript
原生JS实现动态加载js文件并在加载成功后执行回调函数的方法
2020/12/30 Javascript
vue以组件或者插件的形式实现throttle或者debounce
2019/05/22 Javascript
vue实现Excel文件的上传与下载功能的两种方式
2019/06/28 Javascript
在Vue项目中,防止页面被缩放和放大示例
2019/10/28 Javascript
javascript 内存模型实例详解
2020/04/18 Javascript
vue keep-alive实现多组件嵌套中个别组件存活不销毁的操作
2020/10/30 Javascript
[04:02]DOTA2上海特锦赛小组赛第二日recap精彩回顾
2016/02/28 DOTA
python enumerate函数的使用方法总结
2017/11/15 Python
Python实现获取系统临时目录及临时文件的方法示例
2019/06/26 Python
浅谈Python 递归算法指归
2019/08/22 Python
将tensorflow.Variable中的某些元素取出组成一个新的矩阵示例
2020/01/04 Python
美国知名的百货清仓店:Neiman Marcus Last Call
2016/08/03 全球购物
Skechers越南官方网站:来自美国的运动休闲品牌
2021/02/22 全球购物
预备党员转正思想汇报
2014/01/12 职场文书
项目负责人任命书
2014/06/04 职场文书
超市理货员岗位职责
2014/07/04 职场文书
讲文明懂礼貌演讲稿
2014/09/11 职场文书
Python竟然能剪辑视频
2021/05/25 Python
win10蓝屏0xc0000001安全模式进不了怎么办?win10出现0xc0000001的解决方法
2022/08/05 数码科技