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

原创 2012年03月29日 14:01:35

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

每天挖一点,今天挖一挖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的指针数组的实现。

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

PHP内核每天挖一点-数组的底层结构

(文中任何描述以及阐述不正确的地方希望大家不令赐教) 关于PHP中的数组确实是太灵活了,那么数组究竟在底层的结构是怎样的?这个是比较让人好奇的。在PHP中数组的实现是通过哈希表实现的,这个在动态...

PHP内核每天挖一点-前言

php是由C实现的一种动态语言,所谓动态语言以及静态语言其实区分的标准就是变量数据类型确定的时间,动态语言在运行时候才会确定变量类型而静态语言则在编译期就可以确定。打算这段时间系统的看一下PHP的源码...

mysql语句实现php函数explode()的分割字符串功能

首先:arry explode(string $sep,string $string[,int $limit]);函数功能是用指定分隔符$sep把字符串分割成数组。 i.e.  $string =...

每天写一点代码----双栈实现队列

问题描述: 用两个堆栈模拟队列的操作(入队与出队)。 思路: 我们都知道队列是一种 先进先出(FIFO) 的数据结构, 栈是一种 后进先出(LIFO) 的数据结构, 要用两个栈实现一个队列,可以这样设...

【每天一点Linux】Linux下进度条的实现

在Linux的学习中,我们经常会听到缓冲区的概念。那么什么是缓冲区呢?在之前学习C语言和C++的时候,我们知道这相当于一个容器,用来暂时存储我们的输入输出数据。相对来说,这块空间可以提高我们I/O的效...

每天一点python——八大排序算法的Python实现

1、插入排序 描述 插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为O(n^2)。是稳定的排序方法。插入算...

每天写一点代码----双队列实现栈

问题描述: 用两个队列模拟堆栈的操作(入栈与出栈) 思路: 我们知道队列是FIFO(先进先出),而栈是后进先出(LIFO) 要用两个队列实现堆栈我们可以这样做: 假设有A.B两个队列,开始都为空。那么...

php使用explode分割字符串新手容易忽略的问题

本文将介绍php explode方法的使用及新手使用容易忽略的问题。

PHP字符串分割函数explode,strtok,str_spli的用法

PHP字符串函数在PHP网站开发中广泛使用,比如使用PHP字符串函数对字符串分割、截取、匹配、替换等处理。PHP字符串函数对于PHP入门学习者来说必不可少,本文将主要介绍PHP字符串分割函数处理心得,...

PHP函数implode()与explode()的用法

implode()和explode()二个函数,是字符串和数组之间的互相转换函数。   implode()将数组转成字符串           PHP函数implode()把数组元素组合为一个...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)