免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 7184 | 回复: 6
打印 上一主题 下一主题

深入理解PHP之--字符串搜索系列函数的实现 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-02-26 17:38 |只看该作者 |倒序浏览
作者:鸭嘴
原文出处:PHP源代码分析-字符串搜索系列函数实现详解

今天和同事在讨论关键字过虑的算法实现,前几天刚看过布隆过滤算法,于是就想起我们公司内部的查找关键字程序,好奇是怎么实现的。于是查找了一下源代码,原来可以简单地用stripos函数查找,
stripos原型如下:
int stripos ( string $haystack, string $needle [, int $offset] )
一般地都会建一个关键词库,然后把用户输入的内容作为haystack,然后循环遍历一下关键词库,把每个关键词作为needle,如果存在的话则会返回关键字在输入的内容中的位置。
于是查找了一下PHP源代码关于这个函数的实现,如果想知道一个函数在PHP的哪个模块的话可以简单写一个函数get_module.php
<?php

if(substr(php_sapi_name(),0,6)=='cli'){
&nbsp;&nbsp;&nbsp;&nbsp;//命令行模式

&nbsp;&nbsp;&nbsp;&nbsp;global $argv;
&nbsp;&nbsp;&nbsp;&nbsp;$function = $argv[1];
}else{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//网页方式

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$function = $_GET['name'];
}
$extensions = get_loaded_extensions();
foreach($extensions as $t){
&nbsp;&nbsp;&nbsp;&nbsp;$modules_funcs = get_extension_funcs($t);
&nbsp;&nbsp;&nbsp;&nbsp;if(in_array($function, (array)$modules_funcs)){
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$is_found = true;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;echo "$function is in $t module\n";
&nbsp;&nbsp;&nbsp;&nbsp;}
}
if(!$is_found)echo "$function not found";

?>


字符串系列的函数属于PHP的标准模块,在ext/standard目录下,string.c文件。

PHP_FUNCTION(stripos)
{
    char *found = NULL;
    char *haystack;
    int haystack_len;
    long offset = 0;
    char *needle_dup = NULL, *haystack_dup;
    char needle_char[2];
    zval *needle;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|l", &haystack, &haystack_len, &needle, &offset) == FAILURE) {
        return;
    }
        检查参数,第一第二个是必选参数,第三个是可选,|表示后面的参数都是可选的。

    if (offset < 0 || offset > haystack_len) {
        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Offset not contained in string");
        RETURN_FALSE;
    }

    if (haystack_len == 0) {
        RETURN_FALSE;
    }

    haystack_dup = estrndup(haystack, haystack_len);
        //与大小写无关,所以先把字符全部转换成小写

    php_strtolower(haystack_dup, haystack_len);

    if (Z_TYPE_P(needle) == IS_STRING) {
               //第二个参数如果是字符串

        if (Z_STRLEN_P(needle) == 0 || Z_STRLEN_P(needle) > haystack_len) {
            efree(haystack_dup);
            RETURN_FALSE;
        }

        needle_dup = estrndup(Z_STRVAL_P(needle), Z_STRLEN_P(needle));
        php_strtolower(needle_dup, Z_STRLEN_P(needle));
                //这个是关键,由php_memnstr实现

        found = php_memnstr(haystack_dup + offset, needle_dup, Z_STRLEN_P(needle), haystack_dup + haystack_len);
    } else {
               //第二个参数是数字的话,则强制转换成(char)类型

        switch (Z_TYPE_P(needle)) {
            case IS_LONG:
            case IS_BOOL:
                needle_char[0] = tolower((char) Z_LVAL_P(needle));
                break;
            case IS_DOUBLE:
                needle_char[0] = tolower((char) Z_DVAL_P(needle));
                break;
            default:
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "needle is not a string or an integer");
                efree(haystack_dup);
                RETURN_FALSE;
                break;

        }
        needle_char[1] = '\0';
        found = php_memnstr(haystack_dup + offset,
                            needle_char,
                            sizeof(needle_char) - 1,
                            haystack_dup + haystack_len);
    }

    efree(haystack_dup);
    if (needle_dup) {
        efree(needle_dup);
    }

    if (found) {
                //如何找到,则返回在这个字符串中的位置

        RETURN_LONG(found - haystack_dup);
    } else {
        RETURN_FALSE;
    }
}


查找函数是由php_memstr实现的,在main目录下的php.h文件
#define php_memnstr zend_memnstr

所以真正的函数是zend_memnstr,在zend/目录下面的zend_operators.h,

static inline char *
zend_memnstr(char *haystack, char *needle, int needle_len, char *end)
{
    char *p = haystack;
    char ne = needle[needle_len-1];

    end -= needle_len;

    while (p <= end) {
        if ((p = (char *)memchr(p, *needle, (end-p+1))) && ne == p[needle_len-1]) {
            if (!memcmp(needle, p, needle_len-1)) {
                return p;
            }
        }

        if (p == NULL) {
            return NULL;
        }

        p++;
    }

    return NULL;
}


查到这里就能看到实现搜索的原理了,主要用了一个while循环和两个C的函数memchr和memcmp。
先用第一个函数查找needle的第一个字符在haystack中出现的位置,然后调用memcmp,从这个位置开始比较needle和haystack,如果相同就返回这个位置,没有的话再把指针指向haystack的下一位再进行比较,一直到最后。
不过这个搜索只是简单地调用了memchr和memcmp函数,至于memcmp用了什么算法比较两个字符串就不太清楚,我们知道在一个长度为n的字符串里面查找字符串为m的字符串,那么最坏的时间复杂度是O(n*m),上网搜了一下memcmp,不过没有找到他的实现原理。后来想了一下发现这个其实就是最简单的两次循环遍历进行比较。看了一下PHP的其他几个字符串查找函数strstr,stristr,strpos,strrpos,strripos 等函数都是调用zend_memnstr这个函数实现的,只是在返回的时候内容不同而已。

[ 本帖最后由 jackywdx 于 2009-2-26 17:40 编辑 ]

论坛徽章:
0
2 [报告]
发表于 2009-02-26 20:30 |只看该作者
似乎算法还可以再优化

[ 本帖最后由 bs 于 2009-2-26 20:33 编辑 ]

论坛徽章:
0
3 [报告]
发表于 2009-02-27 08:55 |只看该作者
很深入,很算法

很新浪


2分入手

论坛徽章:
0
4 [报告]
发表于 2009-02-27 10:38 |只看该作者
呵呵,是啊。我觉得这个算法还可以优化。

论坛徽章:
0
5 [报告]
发表于 2009-03-04 16:24 |只看该作者
哇!好也

论坛徽章:
1
2015年迎新春徽章
日期:2015-03-04 09:48:00
6 [报告]
发表于 2010-07-23 14:02 |只看该作者
版面需要调整一下,有点乱

论坛徽章:
0
7 [报告]
发表于 2010-07-23 17:10 |只看该作者
帮顶,代码要是放在code中就会更好一点
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP