您的位置:首页 > 编程语言 > PHP开发

PHP源代码分析-字符串搜索系列函数实现详解

2011-11-15 13:53 645 查看
今天和同事在讨论关键字过虑的算法实现,前几天刚看过布隆过滤算法,于是就想起我们公司内部的查找关键字程序,好奇是怎么实现的。于是查找了一下源代码,原来可以简单地用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'){
    //命令行模式

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

       $function = $_GET['name'];
}
$extensions = get_loaded_extensions();
foreach($extensions as $t){
    $modules_funcs = get_extension_funcs($t);
    if(in_array($function, (array)$modules_funcs)){
        $is_found = true;
        echo "$function is in $t module\n";
    }
}
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这个函数实现的,只是在返回的时候内容不同而已。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: