PHP源码之explode使用说明


Posted in PHP onAugust 05, 2011

当我们需要将一个数组根据某个字符或字串进行分割成数组的时候,explode用的很happy,但是你知道~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)
PHP 相关文章推荐
simplehtmldom Doc api帮助文档
Mar 26 PHP
PHP ? EasyUI DataGrid 资料存的方式介绍
Nov 07 PHP
解析phpstorm + xdebug 远程断点调试
Jun 20 PHP
PHP防止跨域提交表单
Nov 01 PHP
php实现可以设置中奖概率的抽奖程序代码分享
Jan 19 PHP
php的zip解压缩类pclzip使用示例
Mar 14 PHP
php实现CSV文件导入和导出
Oct 24 PHP
是 WordPress 让 PHP 更流行了 而不是框架
Feb 03 PHP
php mysql获取表字段名称和字段信息的三种方法
Nov 13 PHP
Yii2学习笔记之汉化yii设置表单的描述(属性标签attributeLabels)
Feb 07 PHP
PHP实现登录验证码校验功能
May 17 PHP
php 自定义函数实现将数据 以excel 表格形式导出示例
Nov 13 PHP
PHP在获取指定目录下的目录,在获取的目录下面再创建文件,多平台
Aug 03 #PHP
php中获取远程客户端的真实ip地址的方法
Aug 03 #PHP
用PHP实现的四则运算表达式计算实现代码
Aug 02 #PHP
PHP5 的对象赋值机制介绍
Aug 02 #PHP
利用PHP实现智能文件类型检测的实现代码
Aug 02 #PHP
10条PHP高级技巧[修正版]
Aug 02 #PHP
PHP获取url的函数代码
Aug 02 #PHP
You might like
全国FM电台频率大全 - 17 湖北省
2020/03/11 无线电
第四章 php数学运算
2011/12/30 PHP
利用php获得flv视频长度的实例代码
2017/10/26 PHP
php微信开发之谷歌测距
2018/06/14 PHP
初步认识JavaScript函数库jQuery
2015/06/18 Javascript
js jquery获取当前元素的兄弟级 上一个 下一个元素
2015/09/01 Javascript
基于javascript实现窗口抖动效果
2016/01/03 Javascript
jQuery删除节点用法示例(remove方法)
2016/09/08 Javascript
老生常谈JQuery data方法的使用
2016/09/09 Javascript
ExtJs异步无法向外传值和赋值的完美解决办法
2017/06/14 Javascript
Vue.js项目中管理每个页面的头部标签的两种方法
2018/06/25 Javascript
vue轮播组件实现$children和$parent 附带好用的gif录制工具
2019/09/26 Javascript
利用Vue实现简易播放器的完整代码
2020/12/30 Vue.js
[01:55]TI9显影之尘系列 - Evil Geniuses
2019/08/22 DOTA
Django如何实现内容缓存示例详解
2017/09/24 Python
python实现支付宝当面付(扫码支付)功能
2018/05/30 Python
实例分析python3实现并发访问水平切分表
2018/09/29 Python
在Python中定义一个常量的方法
2018/11/10 Python
对python3标准库httpclient的使用详解
2018/12/18 Python
对Python3 pyc 文件的使用详解
2019/02/16 Python
django框架模型层功能、组成与用法分析
2019/07/30 Python
Python实现粒子群算法的示例
2021/02/14 Python
美国最流行的男士时尚网站:Touch of Modern
2018/02/05 全球购物
Beauty Expert美国/加拿大:购买奢侈美容产品
2018/12/05 全球购物
家具厂厂长岗位职责
2014/01/01 职场文书
教师四风对照检查材料思想汇报
2014/09/17 职场文书
大学生在校表现评语
2014/12/31 职场文书
2015年医院护理部工作总结
2015/04/23 职场文书
巴黎圣母院观后感
2015/06/10 职场文书
电台广播稿范文
2015/08/19 职场文书
2016年度优秀辅导员事迹材料
2016/02/26 职场文书
2019年七夕情人节浪漫祝福语大全!
2019/08/08 职场文书
css position fixed 左右双定位的实现代码
2021/04/29 HTML / CSS
Python基础之常用库常用方法整理
2021/04/30 Python
怎么禁用Windows 11快照布局? win11不使用快照布局的技巧
2021/11/21 数码科技
mongoDB数据库索引快速入门指南
2022/03/23 MongoDB