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 相关文章推荐
php foreach、while性能比较
Oct 15 PHP
PHP 文本文章分页代码 按标记或长度(不涉及数据库)
Jun 07 PHP
php使用strtotime和date函数判断日期是否有效代码分享
Dec 25 PHP
ThinkPHP之getField详解
Jun 20 PHP
php中运用http调用的GET和POST方法示例
Sep 29 PHP
PHP对文件进行加锁、解锁实例
Jan 23 PHP
memcache一致性hash的php实现方法
Mar 05 PHP
PHP编程之设置apache虚拟目录
Jul 08 PHP
Thinkphp3.2.3分页使用实例解析
Jul 28 PHP
php微信开发自定义菜单
Aug 27 PHP
php 文件下载 出现下载文件内容乱码损坏的解决方法(推荐)
Nov 16 PHP
tp5.0框架隐藏index.php入口文件及模块和控制器的方法分析
Feb 11 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
php 中文字符入库或显示乱码问题的解决方法
2010/04/12 PHP
php设计模式 Chain Of Responsibility (职责链模式)
2011/06/26 PHP
浅析php面向对象public private protected 访问修饰符
2013/06/30 PHP
PHP 通过Socket收发十六进制数据的实现代码
2013/08/16 PHP
php调用nginx的mod_zip模块打包ZIP文件
2014/06/11 PHP
Codeigniter注册登录代码示例
2014/06/12 PHP
Laravel统一错误处理为JSON的方法介绍
2020/10/18 PHP
关于scrollLeft,scrollTop的浏览器兼容性测试
2013/03/19 Javascript
javascript实现TreeView 无刷新展开的实例代码
2013/07/13 Javascript
使用jquery解析XML示例代码
2014/09/05 Javascript
JavaScript 里的类数组对象
2015/04/08 Javascript
JS验证IP,子网掩码,网关和MAC的方法
2015/07/02 Javascript
AngularJS自定义插件实现网站用户引导功能示例
2016/11/07 Javascript
浅析vue component 组件使用
2017/03/06 Javascript
vue中axios处理http发送请求的示例(Post和get)
2017/10/13 Javascript
浅谈Node.js CVE-2017-14849 漏洞分析(详细步骤)
2017/11/10 Javascript
javascript中的replace函数(带注释demo)
2018/01/07 Javascript
vuex直接赋值的三种方法总结
2018/09/16 Javascript
JQuery Ajax如何实现注册检测用户名
2020/09/25 jQuery
Vue中使用Echarts仪表盘展示实时数据的实现
2020/11/01 Javascript
Python选择排序、冒泡排序、合并排序代码实例
2015/04/10 Python
Python中优化NumPy包使用性能的教程
2015/04/23 Python
Python 批量合并多个txt文件的实例讲解
2018/05/08 Python
Django 中间键和上下文处理器的使用
2019/03/17 Python
关于python多重赋值的小问题
2019/04/17 Python
python对指定字符串逆序的6种方法(小结)
2020/04/02 Python
Python单元测试及unittest框架用法实例解析
2020/07/09 Python
CSS3过渡transition效果实例介绍
2016/05/03 HTML / CSS
婴儿地球:Baby Earth
2018/12/25 全球购物
学习决心书范文
2014/03/11 职场文书
讲解员培训方案
2014/05/04 职场文书
音乐节策划方案
2014/06/09 职场文书
项目工作说明书
2014/07/29 职场文书
同事离别感言
2015/08/04 职场文书
MySQL中日期型单行函数代码详解
2021/06/21 MySQL
你真的了解redis为什么要提供pipeline功能
2021/06/22 Redis