解析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.ini中添加extension=php_mysqli.dll指令的说明
Jun 14 PHP
常用的php ADODB使用方法集锦
Mar 25 PHP
PHP 采集获取指定网址的内容
Jan 05 PHP
php防止sql注入代码实例
Dec 18 PHP
ThinkPHP空模块和空操作详解
Jun 30 PHP
yii实现CheckBox复选框在同一行显示的方法
Dec 03 PHP
PHP 设计模式系列之 specification规格模式
Jan 10 PHP
PHP使用pear实现mail发送功能 windows环境下配置pear
Apr 15 PHP
php连接oracle数据库的核心步骤
May 26 PHP
PHP的中使用非缓冲模式查询数据库的方法
Feb 05 PHP
解决Laravel自定义类引入和命名空间的问题
Oct 15 PHP
php+js实现的拖动滑块验证码验证表单操作示例【附源码下载】
May 27 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
玛琪朵 Macchiato
2021/03/03 咖啡文化
php 破解防盗链图片函数
2008/12/09 PHP
整理的一些实用WordPress后台MySQL操作命令
2013/01/07 PHP
php中OR与|| AND与&amp;&amp;的区别总结
2013/10/26 PHP
php实现查看邮件是否已被阅读的方法
2013/12/03 PHP
php中apc缓存使用示例
2013/12/25 PHP
Linux系统下php获得系统分区信息的方法
2015/03/30 PHP
PHP快速生成各种信息提示框的方法
2016/02/03 PHP
浅析Yii2 GridView 日期格式化并实现日期可搜索教程
2016/04/22 PHP
PHP时间处理类操作示例
2018/09/05 PHP
PHP 实现链式操作
2021/03/09 PHP
Javascript 模式实例 观察者模式
2009/10/24 Javascript
用js来解决ajax读取页面乱码
2010/11/28 Javascript
实例分析javascript中的call()和apply()方法
2014/11/28 Javascript
angularJS Provider、factory、service详解及实例代码
2016/09/21 Javascript
使用微信小程序开发前端【快速入门】
2016/12/05 Javascript
nodejs实例解析(输出hello world)
2017/01/03 NodeJs
微信小程序 自动登陆PHP源码实例(源码下载)
2017/05/08 Javascript
jQuery之动画ajax事件(实例讲解)
2017/07/18 jQuery
微信小程序页面滑动屏幕加载数据效果
2020/11/16 Javascript
对Vue2 自定义全局指令Vue.directive和指令的生命周期介绍
2018/08/30 Javascript
vue 基于element-ui 分页组件封装的实例代码
2018/12/10 Javascript
vue3.0实现插件封装
2020/12/14 Vue.js
[02:49]2014DOTA2电竞也是体育项目! 势要把荣誉带回中国!
2014/07/20 DOTA
Python中pow()和math.pow()函数用法示例
2018/02/11 Python
Python线程同步的实现代码
2018/10/03 Python
python实现移位加密和解密
2019/03/22 Python
SQL Server数据库笔试题和答案
2016/02/04 面试题
请写出一段Python代码实现删除一个list里面的重复元素
2015/12/29 面试题
销售员求职个人的自我评价
2014/02/19 职场文书
“学雷锋活动月”总结
2014/03/09 职场文书
战友聚会主持词
2014/04/02 职场文书
大学生职业生涯规划大赛作品(精品)
2014/09/17 职场文书
2014派出所所长群众路线对照检查材料思想汇报
2014/09/18 职场文书
2014年教研工作总结
2014/12/06 职场文书
暂住证明怎么写
2015/06/19 职场文书