加入收藏 | 设为首页 | 会员中心 | 我要投稿 安卓应用网 (https://www.0791zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > PHP > 正文

深入理解php explode()函数的工作原理

发布时间:2020-05-25 22:38:42 所属栏目:PHP 来源:互联网
导读:当我们需要将一个数组根据某个字符或字串进行分割成数组的时候,explode()函数很好用,本文章向大家介绍php explode()函数的使用方法和工作原理,需要的朋友可以参考一下。

PHP函数explode()把字符串分割为数组

首先,我们来看一个explode()函数的简单实例:

$str = "Hello world. It's a beautiful day.";

print_r (explode(" ",$str));

?>

输出:

Array

(

[0] => Hello

[1] => world.

[2] => It's

[3] => a

[4] => beautiful

[5] => day.

)

以上代码示例就是PHP函数explode()的具体使用方法,但是你知道explode()是怎么工作的么?截取字串的问题,都会避免不了重新分配空间的消耗,explode也是会分配空间的,毫无疑问。

//先来看下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,limit);

} else {

add_index_stringl(return_value,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,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,p2 - p1,1);

//定位到分隔符位置p2+分隔符的长度的位置

//比如,分隔符='|',原字串= ’ab|c',p2 = 2,则p1=2+1=3

p1 = p2 + Z_STRLEN_P(delim);

} while ((p2 = php_memnstr(p1,endp)) != NULL &&

--limit > 1);

//将最后的一个分隔符后面的字串放到结果数组中

//explode('|','avc|sdf'); => array(0 => 'avc',1= > 'sdf')

if (p1 <= endp)

add_next_index_stringl(return_value,endp-p1,1);

}

}

2、limit < 0 :

调用php_explode_negative_limit方法:

PHPAPI void php_explode_negative_limit(zval *delim,long limit)

{

#define EXPLODE_ALLOC_STEP 64

char *p1,*endp;

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

p1 = Z_STRVAL_P(str);

p2 = php_memnstr(Z_STRVAL_P(str),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,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('|',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,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,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)。

(编辑:安卓应用网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读