PHP源码之explode使用说明

前端之家收集整理的这篇文章主要介绍了PHP源码之explode使用说明前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

当我们需要将一个数组根据某个字符或字串进行分割成数组的时候,explode用的很happy,但是你知道~explode是怎么工作的么~~
首先可以肯定的是,explode也是会分配空间的,毫无疑问。
<div class="codetitle"><a style="CURSOR: pointer" data="48379" class="copybut" id="copybut48379" onclick="doCopy('code48379')"> 代码如下:

<div class="codebody" id="code48379">
//文件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,limit);
} else {
add_index_stringl(return_value,1);
}
}

<div class="codetitle"><a style="CURSOR: pointer" data="40093" class="copybut" id="copybut40093" onclick="doCopy('code40093')"> 代码如下:
<div class="codebody" id="code40093">
//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实现的上面出现(所以在查找本函数调用来自本文件方法的时候很方便,几乎无一列外都是在该函数的紧接着的上面^_^),
<div class="codetitle"><a style="CURSOR: pointer" data="18066" class="copybut" id="copybut18066" onclick="doCopy('code18066')"> 代码如下:
<div class="codebody" id="code18066">
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方法
<div class="codetitle"><a style="CURSOR: pointer" data="57587" class="copybut" id="copybut57587" onclick="doCopy('code57587')"> 代码如下:
<div class="codebody" id="code57587">
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');
<div class="codetitle"><a style="CURSOR: pointer" data="39025" class="copybut" id="copybut39025" onclick="doCopy('code39025')"> 代码如下:<div class="codebody" id="code39025">
//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)

explode

猜你在找的PHP相关文章