PHP内核每天挖一点-explode的实现

文中任何描述以及阐述不正确的地方希望大家不令赐教

每天挖一点,今天挖一挖explode函数的实现。首先看看手册里面关于explode的定义:

explode -- 使用一个字符串分割另一个字符串
说明 array explode ( string separator, string string [, int limit] )

此函数返回由字符串组成的数组,每个元素都是 string 的一个子串,它们被字符串 separator 作为边界点分割出来。如果设置了 limit 参数,则返回的数组包含最多 limit 个元素,而最后那个元素将包含 string 的剩余部分。

如果 separator 为空字符串(""),explode() 将返回 FALSE。如果 separator 所包含的值在 string 中找不到,那么 explode() 将返回包含 string 单个元素的数组。

如果 limit 参数是负数,则返回除了最后的 -limit 个元素外的所有元素。此特性是 PHP 5.1.0 中新增的。 
由于历史原因,虽然 implode() 可以接收两种参数顺序,但是 explode() 不行。你必须保证 separator 参数在 string 参数之前才行。
注意: 参数 limit 是在 PHP 4.0.1 中加入的。


这里有两点,第一返回一定是个数组,不管你找到还是找不到那个分隔符, 第二分隔符如果空串在5.3.10里面是会报个warning的。explode函数是超级强大的, 作为PHP程序员也是幸福的,因为至少你可以explode,不用像C里面的strtok那样去干活,explode究竟如何实现的。explode属于PHP内部标准函数实现在string.c

先看一下原型函数:

PHP_FUNCTION(explode)
{
	char *str, *delim;              //定义两个字符串指针用于接收来自扩展传入的参数
	int str_len = 0, delim_len = 0; //字符串长度
	long limit = LONG_MAX; /* No limit */
	zval zdelim, zstr;              //zval后面会重点挖一挖,是个PHP内在的数据结构,PHP语言特性实现的关键
	
        //开始接受来自外部的赋值
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|l", &delim, &delim_len, &str, &str_len, &limit) == FAILURE) {
		return;
	}
	
	if (delim_len == 0) {
		//分隔符为空的时候就会到这里,并没有返回false而是直接报错告警
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Empty delimiter");
		RETURN_FALSE;
	}
        //初始化返回值,return_value也是一个要重点挖掘的结构,在PHP扩展开发以及内部函数使用中的返回值结构
	array_init(return_value);

	if (str_len == 0) {
	  	if (limit >= 0) {
			//如果待切割字符串长度0,limit大于等于0时返回数组仅包含一个元素""空串
                        add_next_index_stringl(return_value, "", sizeof("") - 1, 1);
		} 
		return;
	}
        //这里开始包装两个字符串到zval这个结构中
	ZVAL_STRINGL(&zstr, str, str_len, 0);
	ZVAL_STRINGL(&zdelim, delim, delim_len, 0);
	if (limit > 1) {
                //开始分割
		php_explode(&zdelim, &zstr, return_value, limit);
	} else if (limit < 0) {
		//limit -1特性的实现
                php_explode_negative_limit(&zdelim, &zstr, return_value, limit);
	} else {
		//limit==0,返回的依旧是数组包含一个元素 0
                add_index_stringl(return_value, 0, str, str_len, 1);
	}
}
这里有几个很重要的宏以及数据结构备注一下,后续会深入阐述。
zend_parse_parameters 用于接受PHP API函数中的输入参数
return_value          PHP API中的返回值结构体(在PHP中变量类型最后都对应一个结构体,而这个结构体中的共用体部分正是实现了PHP数据类型的动态性)

explode实现的基本算法(php_explode)

基本思路就是从首指针开始往尾部移动,每次查找与分隔符的位置就把前面部分的值存入return_value(一个哈希表结构)中

PHPAPI void php_explode(zval *delim, zval *str, zval *return_value, long limit)
{
	char *p1, *p2, *endp;

	endp = Z_STRVAL_P(str) + Z_STRLEN_P(str);

        //计算首指针,用于后续循环
	p1 = Z_STRVAL_P(str);

        //计算第一个分隔符的指针
	p2 = php_memnstr(Z_STRVAL_P(str), Z_STRVAL_P(delim), Z_STRLEN_P(delim), endp);

	//如果没有分隔符则返回全部字符串
	if (p2 == NULL) {
		add_next_index_stringl(return_value, p1, Z_STRLEN_P(str), 1);
	} else {
                //有的话开始查询分隔符的位置,循环把分隔符之间的内容拷贝到return_val这个哈希结构中
		do {
			add_next_index_stringl(return_value, p1, p2 - p1, 1);
			p1 = p2 + Z_STRLEN_P(delim);
                //php_memnstr用于查找分隔符并返回第一个分隔符位置的函数,后面可以看一下他的实现
		} while ((p2 = php_memnstr(p1, Z_STRVAL_P(delim), Z_STRLEN_P(delim), endp)) != NULL &&
				 --limit > 1);

		if (p1 <= endp)
			add_next_index_stringl(return_value, p1, endp-p1, 1);
	}
}

php_memnstr(查找字符串(分隔符)位置的实现)

zend_memnstr(char *haystack, char *needle, int needle_len, char *end)
{
     字符首指针
     char *p = haystack;
     最后一个字符
     char ne = needle[needle_len-1]; 
     结束字符串位置,只需到end-needle_len位置即可
     end -= needle_len;

    while (p <= end) {
        在数组的前n个字节中搜索字符 memchr(p, *needle, (end-p+1))
        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;
}

explode limit 参数还支持负数,当为负数的时候调用的函数是php_explode_negative_limit,他的实现算法很简单,首先通过php_memnstr把所有分割的字符串的首地址全部记录在一个position指针数组中,并且记录数组的长度found,然后通过found+limit即可得到需要返回的所有字符串的指针数组的,看一下实现


PHPAPI void php_explode_negative_limit(zval *delim, zval *str, zval *return_value, long limit)
{
#define EXPLODE_ALLOC_STEP 64
	char *p1, *p2, *endp;

	endp = Z_STRVAL_P(str) + Z_STRLEN_P(str);

	p1 = Z_STRVAL_P(str);
	p2 = php_memnstr(Z_STRVAL_P(str), Z_STRVAL_P(delim), Z_STRLEN_P(delim), endp);

	if (p2 == NULL) {
		/*
		do nothing since limit <= -1, thus if only one chunk - 1 + (limit) <= 0
		by doing nothing we return empty array
		*/
	} else {
		int allocated = EXPLODE_ALLOC_STEP, found = 0;
		long i, to_return;
		char **positions = emalloc(allocated * sizeof(char *));

		positions[found++] = p1;
		do {
			if (found >= allocated) {
				allocated = found + EXPLODE_ALLOC_STEP;/* make sure we have enough memory */
				positions = erealloc(positions, allocated*sizeof(char *));
			}
			positions[found++] = p1 = p2 + Z_STRLEN_P(delim);
		} while ((p2 = php_memnstr(p1, Z_STRVAL_P(delim), Z_STRLEN_P(delim), endp)) != NULL);

		to_return = limit + found;
		/* limit is at least -1 therefore no need of bounds checking : i will be always less than found */
		for (i = 0;i < to_return;i++) { /* this checks also for to_return > 0 */
			add_next_index_stringl(return_value, positions[i],
					(positions[i+1] - Z_STRLEN_P(delim)) - positions[i],
					1
				);
		}
		efree(positions);
	}
#undef EXPLODE_ALLOC_STEP
}

上面这段就不加注释了,读者可以自己研读一下代码,比较之前的explode的实现,其实关键的地方就是多了一个position的指针数组的实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值