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使用explode分割字符串新手容易忽略的问题

本文将介绍php explode方法的使用及新手使用容易忽略的问题。
  • fdipzone
  • fdipzone
  • 2016年02月28日 12:07
  • 18994

php中解决explode()函数无法分割tab键问题

前几天碰到一个这样一个问题:要求读取文件中的每一行,并把每列分割到数组中去,其中每列是以tab键("\t")分隔开来。 很自然的想到先用fgets()读取文件中的每一行,并用explode()函数进...
  • qlzx_syzx
  • qlzx_syzx
  • 2016年11月13日 22:02
  • 1900

PHP explode() 函数

定义和用法 explode() 函数把字符串打散为数组。 注释:"separator" 参数不能是空字符串。 注释:该函数是二进制安全的。 语法 explode(se...
  • wuli2496
  • wuli2496
  • 2016年03月22日 15:49
  • 503

PHP实现内部函数explode

摘要: PHP实现内部函数explode,总是喜欢这样考验算法吗 PHPAPI void php_explode(zval *delim, zval *str, zval *return_valu...
  • gb4215287
  • gb4215287
  • 2016年12月11日 19:03
  • 371

利用php的explode函数将字符串按分隔符(比如空格)分拆并组装在数组中-----要考虑连续空格问题

看php:        结果: Array (     [0] => Hello     [1] => world.     [2] => I     [3] => love ...
  • stpeace
  • stpeace
  • 2016年02月25日 23:08
  • 9449

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

(文中任何描述以及阐述不正确的地方希望大家不令赐教) 每天挖一点,今天挖一挖explode函数的实现。首先看看手册里面关于explode的定义: explode -- 使用一个字符串分割另一个...
  • nituizi2012
  • nituizi2012
  • 2012年03月29日 14:01
  • 1628

PHP中explode()和implode()函数讲解和实战用法

PHP中explode()和implode()函数讲解以及在添加好友和关注实战项目中的用法
  • Asce_zz
  • Asce_zz
  • 2017年11月28日 21:19
  • 169

PHP内核每天挖一点-前言

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

分享一个关于php中explode()函数输出实例(简单的)

一个简单关于php中explode()的简单输出处理 $setting="奥斯卡梦幻影城|1,爱菲堡宫殿|2,中国风特色馆|3,百万纯美花海|4,半山半岛湖|5,普罗旺斯浪漫4A景区|6,拍遍株洲|...
  • lose_ever
  • lose_ever
  • 2013年03月19日 13:42
  • 1030

php中explode与split的区别介绍

首先来看下两个方法的定义:  函数原型:array split (string $pattern, string $string [, int $limit])  函数原型:array e...
  • weiqubo
  • weiqubo
  • 2015年06月19日 17:50
  • 908
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:PHP内核每天挖一点-explode的实现
举报原因:
原因补充:

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