从源码去理解PHP的explode()函数

当我们需要将一个数组根据某个字符或字串进行分割成数组的时候,explode()函数很好用,但是你知道explode()是怎么工作的么?截取字串的问题,都会避免不了重新分配空间的消耗,explode也是会分配空间的,毫无疑问。老品牌娱乐城

//文件1:ext/standard/string.c
//先来看下explode的源代码
PHP_FUNCTION(explode)
{
	char *str, *delim;
	int str_len = 0, delim_len = 0;
	long limit = LONG_MAX; /* No limit */
	zval zdelim, zstr;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|l", &delim, &delim_len, &str, &str_len, &limit) == FAILURE) {
		return;
	}
	if (delim_len == 0) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Empty delimiter");
		RETURN_FALSE;
	}

	//这里会开辟一个数组,用来存放分割后的数据
	array_init(return_value);
		//因为这个,我们用explode(‘|‘, ‘‘);成为了合法的
		if (str_len == 0) {
			if (limit >= 0) {
				add_next_index_stringl(return_value, "", sizeof("") - 1, 1);
			}
			return;
		}

		//下面这两个是将原字串和分割符都构建成_zval_struct 结构,
		//ZVAL_STRINGL会分配空间哦~~源代码随后贴出
		ZVAL_STRINGL(&zstr, str, str_len, 0);
		ZVAL_STRINGL(&zdelim, delim, delim_len, 0);
		//limit值是explode中允许传递的explode的第三个参数,它允许正负
		if (limit > 1) {
			php_explode(&zdelim, &zstr, return_value, limit);
		} else if (limit < 0) {
			php_explode_negative_limit(&zdelim, &zstr, return_value, limit);
		} else {
			add_index_stringl(return_value, 0, str, str_len, 1);
		}
	}

再来看一段:

//ZVAL_STRINGL的源代码:
//文件2:zend/zend_API.c
#define ZVAL_STRINGL(z, s, l, duplicate) {    	const char *__s=(s); int __l=l;        	Z_STRLEN_P(z) = __l;                	Z_STRVAL_P(z) = (duplicate?estrndup(__s, __l):(char*)__s);	Z_TYPE_P(z) = IS_STRING;            }
	....
//estrndup才是主菜:
//文件3:zend/zend_alloc.h
#define estrndup(s, length)    _estrndup((s), (length) ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC)
	....
//_estrndup的实现: zend/zend_alloc.c
ZEND_API char *_estrndup(const char *s, uint length ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
{
	char *p;
	p = (char *) _emalloc(length+1 ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
	if (UNEXPECTED(p == NULL)) {
		return p;
	}
	memcpy(p, s, length);   //分配空间
	p[length] = 0;
	return p;
}
//另外在substr和strrchr strstr中用到的ZVAL_STRING也是使用了上诉的实现

下面根据explode的第三个参数limit来分析调用:条件对应的是explode中最后的三行,对limit条件的不同。注: limit在缺省的时候(没有传递),他的默认值是LONG_MAX,也就是属于分支1的情况。

1、limit > 1 :

调用php_explode方法,该方法也可以在ext/standard/string.c中找到,并且是紧接着explode实现的上面出现(所以在查找本函数中调用来自本文件的方法的时候很方便,几乎无一列外都是在该函数的紧接着的上面^_^)。

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);
	//下面这个是获得分割符在str中的位置,可以看到在strrpos和strpos中也用到了这个方法去定位
	p2 = php_memnstr(Z_STRVAL_P(str), Z_STRVAL_P(delim), Z_STRLEN_P(delim), endp);

	if (p2 == NULL) {
		//因为这个,所以当我们调用explode(‘|‘, ‘abc‘);是合法的,出来的的就是array(0 => ‘abc‘)
		add_next_index_stringl(return_value, p1, Z_STRLEN_P(str), 1);
	} else {
		//依次循环获得下一个分隔符的位置,直到结束
		do {
		//将得到的子字串(上个位置到这个位置中间的一段,第一次的时候上个位置就是开始
		add_next_index_stringl(return_value, p1, p2 - p1, 1);
		//定位到分隔符位置p2+分隔符的长度的位置
		//比如,分隔符=‘|‘, 原字串= ’ab|c‘, p2 = 2,  则p1=2+1=3
		p1 = p2 + Z_STRLEN_P(delim);
	} while ((p2 = php_memnstr(p1, Z_STRVAL_P(delim), Z_STRLEN_P(delim), endp)) != NULL &&
                 --limit > 1);
	//将最后的一个分隔符后面的字串放到结果数组中
	//explode(‘|‘, ‘avc|sdf‘);   => array(0 => ‘avc‘, 1= > ‘sdf‘)
		if (p1 <= endp)
			add_next_index_stringl(return_value, p1, endp-p1, 1);
	}
}

2、limit < 0 :

调用php_explode_negative_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) {
	//它这里竟然没有处理,那explode(‘|‘, ‘abc‘, -1) 就成非法的了,获得不了任何值
		/*
		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的声明,这个数组是用来保存所有子字串的读取位置
		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
}

3、limit = 1 or limit = 0 :

当所有第一和第二条件都不满足的时候,就进入的这个分支,这个分支很简单就是将源字串放到输出数组中,explode(‘|‘, ‘avc|sd‘, 1) or explode(‘|‘, ‘avc|sd‘, 0)  都将返回array(0 => ‘avc|sd‘);

//add_index_stringl源代码
//文件4:zend/zend_API.c
ZEND_API int add_next_index_stringl(zval *arg, const char *str, uint length, int duplicate) /* {{{ */
{
	zval *tmp;
	MAKE_STD_ZVAL(tmp);
	ZVAL_STRINGL(tmp, str, length, duplicate);
	return zend_hash_next_index_insert(Z_ARRVAL_P(arg), &tmp, sizeof(zval *), NULL);
}
//zend_hash_next_index_insert
//zend/zend_hash.h
#define zend_hash_next_index_insert(ht, pData, nDataSize, pDest)          _zend_hash_index_update_or_next_insert(ht, 0, pData, nDataSize, pDest, HASH_NEXT_INSERT ZEND_FILE_LINE_CC)
//zend/zend_hash.c
///太长了~~~~不贴了

可见(不包含分配空间这些),当limit>1的时候,效率是O(N)【N为limit值】,当limit < 0的时候,效率是O(N+M)【N为limit值, M 为分割符出现次数】,当limit=1 or  limit=0 的时候, 效率是O(1)。

时间: 2024-08-10 17:20:00

从源码去理解PHP的explode()函数的相关文章

android四大组件之Activity - (1)从源码中理解并巧用onWindowFocusChanged(boolean hasFocus)

这里开始到后面,想趁着有时间,将Android四大组件和一些系统组件做一些总结和记录.由于网上已经有很多写的很好并且总结也全面的文章.小弟我也囊中羞涩不敢献丑,就记录一些自己觉得重要的有用的知识点,顺便大家一起学习讨论啥的也好 Activity作为四大组件之一,对于整个Android开发有多重要就无需赘言了.关于它的生命周期,这里借用下官网的图,便一目了然: 那么它的生命周期和我们所说的onWindowFocusChanged(boolean hasFocus)方法有何关系? Activity生

storm源码之理解Storm中Worker、Executor、Task关系【转】

[原]storm源码之理解Storm中Worker.Executor.Task关系 Storm在集群上运行一个Topology时,主要通过以下3个实体来完成Topology的执行工作:1. Worker(进程)2. Executor(线程)3. Task 下图简要描述了这3者之间的关系:                                                    1个worker进程执行的是1个topology的子集(注:不会出现1个worker为多个topology服

jQuery源码学习笔记:扩展工具函数

// 扩展工具函数 jQuery.extend({ // http://www.w3school.com.cn/jquery/core_noconflict.asp // 释放$的 jQuery 控制权 // 许多 JavaScript 库使用 $ 作为函数或变量名,jQuery 也一样. // 在 jQuery 中,$ 仅仅是 jQuery 的别名,因此即使不使用 $ 也能保证所有功能性. // 假如我们需要使用 jQuery 之外的另一 JavaScript 库,我们可以通过调用 $.noC

Hadoop源码学习笔记(2) ——进入main函数打印包信息

Hadoop源码学习笔记(2) ——进入main函数打印包信息 找到了main函数,也建立了快速启动的方法,然后我们就进去看一看. 进入NameNode和DataNode的主函数后,发现形式差不多: public static void main(String args[]) {     try {       StringUtils.startupShutdownMessage(DataNode.class, args, LOG);       DataNode datanode = crea

关于spring的源码的理解

从最基础的Hello World开始. spring的Hello World就三行代码: public void test() { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); SomeBean someBean= (SomeBean) context.getBean("someBean"); someBean.doSomething(); } 这个hell

从源码上理解Netty并发工具-Promise

前提 最近一直在看Netty相关的内容,也在编写一个轻量级的RPC框架来练手,途中发现了Netty的源码有很多亮点,某些实现甚至可以用苛刻来形容.另外,Netty提供的工具类也是相当优秀,可以开箱即用.这里分析一下个人比较喜欢的领域,并发方面的一个Netty工具模块 - Promise. 环境版本: Netty:4.1.44.Final JDK1.8 Promise简介 Promise,中文翻译为承诺或者许诺,含义是人与人之间,一个人对另一个人所说的具有一定憧憬的话,一般是可以实现的. io.n

从源码来理解ArrayList和LinkedList区别

从源码理解ArrayList和LinkedList区别 ArrayList ArrayList默认容量为10,实质是一个数组用于存放元素,size表示ArrayList所包含的元素个数. ArrayList的3个构造函数,第一个构造函数指定容量,第二个构造函数默认为一个空数组,第三个构造函数是从集合c转变为Arraylist的数组 add函数中,先确认数组容量够不够,一开始如果默认初始化(也就是调用第二个构造函数),添加元素时grow出来数组容量为10,构造了一个新的数组并且copy原来数组元素

hashMap 源码解读理解实现原理和hash冲突

hashMap 怎么说呢. 我的理解是 外表是一个set 数组,无序不重复 . 每个set元素是一个bean ,存着一对key value 看看代码吧 package test; import java.util.HashMap; import java.util.Map.Entry; public class HashMaptest { public static void main(String[] args) { HashMap<String, String> map = new Has

从源码角度理解android动画Interpolator类的使用

做过android动画的人对Interpolator应该不会陌生,这个类主要是用来控制android动画的执行速率,一般情况下,如果我们不设置,动画都不是匀速执行的,系统默认是先加速后减速这样一种动画执行速率. android通过Interpolator类来让我们自己控制动画的执行速率,还记得上一篇博客中我们使用属性动画实现的旋转效果吗?在不设置Interpolator的情况下,这个动画是先加速后减速,我们现在使用android系统提供的类LinearInterpolator来设置动画的执行速率