解析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 相关文章推荐
让你的网站首页自动选择语言转跳
Dec 06 PHP
PHP数组实例总结与说明
Aug 23 PHP
PHP Class&amp;Object -- 解析PHP实现二叉树
Jun 25 PHP
PHP实例分享判断客户端是否使用代理服务器及其匿名级别
Jun 04 PHP
PHP中strlen()和mb_strlen()的区别浅析
Jun 19 PHP
php使用pclzip类实现文件压缩的方法(附pclzip类下载地址)
Apr 30 PHP
php图片上传类 附调用方法
May 15 PHP
PHP序列化操作方法分析
Sep 28 PHP
Laravel框架中VerifyCsrfToken报错问题的解决
Aug 30 PHP
PHP实现获取ip地址的5种方法,以及插入用户登录日志操作示例
Feb 28 PHP
PHP单例模式数据库连接类与页面静态化实现方法
Mar 20 PHP
PHP笛卡尔积实现原理及代码实例
Dec 09 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 和 MySQL 基础教程(三)
2006/10/09 PHP
php db类库进行数据库操作
2009/03/19 PHP
php读取目录所有文件信息dir示例
2014/03/18 PHP
php对象在内存中的存在形式分析
2015/02/03 PHP
php生成图片验证码的实例讲解
2015/08/03 PHP
Zend Framework上传文件重命名的实现方法
2016/11/25 PHP
js 有框架页面跳转(target)三种情况下的应用
2013/04/09 Javascript
jQuery中die()方法用法实例
2015/01/19 Javascript
js动态创建及移除div的方法
2015/06/03 Javascript
javascript实现在线客服效果
2015/07/15 Javascript
Clipboard.js 无需Flash的JavaScript复制粘贴库
2015/10/02 Javascript
jQuery animate easing使用方法图文详解
2016/06/17 Javascript
简单谈谈axios中的get,post方法
2017/06/25 Javascript
Angular4学习笔记之实现绑定和分包
2017/08/01 Javascript
js es6系列教程 - 新的类语法实战选项卡(详解)
2017/09/02 Javascript
原生JS+HTML5实现跟随鼠标一起流动的粒子动画效果
2018/05/03 Javascript
vue使用Element组件时v-for循环里的表单项验证方法
2018/06/28 Javascript
[00:27]DOTA2荣耀之路2:Patience from zhou!
2018/05/24 DOTA
python正则匹配查询港澳通行证办理进度示例分享
2013/12/27 Python
python使用socket进行简单网络连接的方法
2015/04/29 Python
python中defaultdict的用法详解
2017/06/07 Python
Python_LDA实现方法详解
2017/10/25 Python
详解Python 装饰器执行顺序迷思
2018/08/08 Python
python 计算积分图和haar特征的实例代码
2019/11/20 Python
python 经典数字滤波实例
2019/12/16 Python
Kears 使用:通过回调函数保存最佳准确率下的模型操作
2020/06/17 Python
Python3自带工具2to3.py 转换 Python2.x 代码到Python3的操作
2021/03/03 Python
H5最强接口之canvas实现动态图形功能
2019/05/31 HTML / CSS
阿拉伯时尚购物网站:Nisnass
2021/02/07 全球购物
建筑专业毕业生推荐信
2013/11/21 职场文书
微信营销策划方案
2014/02/24 职场文书
《美丽的小路》教学反思
2014/02/26 职场文书
公司总经理岗位职责
2014/03/15 职场文书
2014年小学校长工作总结
2014/12/08 职场文书
谢师宴邀请函
2015/02/02 职场文书
2016年小学圣诞节活动总结
2016/03/31 职场文书