PHP内核每天挖一点-explode的实现
(文中任何描述以及阐述不正确的地方希望大家不令赐教)
每天挖一点,今天挖一挖explode函数的实现。首先看看手册里面关于explode的定义:
explode -- 使用一个字符串分割另一个字符串
说明 array explode ( string separator, string string [, int limit] )
此函数返回由字符串组成的数组,每个元素都是 string 的一个子串,它们被字符串 separator 作为边界点分割出来。如果设置了 limit 参数,则返回的数组包含最多 limit 个元素,而最后那个元素将包含 string 的剩余部分。
如果 separator 为空字符串(""),explode() 将返回 FALSE。如果 separator 所包含的值在 string 中找不到,那么 explode() 将返回包含 string 单个元素的数组。
如果 limit 参数是负数,则返回除了最后的 -limit 个元素外的所有元素。此特性是 PHP 5.1.0 中新增的。
由于历史原因,虽然 implode() 可以接收两种参数顺序,但是 explode() 不行。你必须保证 separator 参数在 string 参数之前才行。
注意: 参数 limit 是在 PHP 4.0.1 中加入的。
这里有两点,第一返回一定是个数组,不管你找到还是找不到那个分隔符, 第二分隔符如果空串在5.3.10里面是会报个warning的。explode函数是超级强大的, 作为PHP程序员也是幸福的,因为至少你可以explode,不用像C里面的strtok那样去干活,explode究竟如何实现的。explode属于PHP内部标准函数实现在string.c
先看一下原型函数:
PHP_FUNCTION(explode) { char *str, *delim; //定义两个字符串指针用于接收来自扩展传入的参数 int str_len = 0, delim_len = 0; //字符串长度 long limit = LONG_MAX; /* No limit */ zval zdelim, zstr; //zval后面会重点挖一挖,是个PHP内在的数据结构,PHP语言特性实现的关键 //开始接受来自外部的赋值 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|l", &delim, &delim_len, &str, &str_len, &limit) == FAILURE) { return; } if (delim_len == 0) { //分隔符为空的时候就会到这里,并没有返回false而是直接报错告警 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Empty delimiter"); RETURN_FALSE; } //初始化返回值,return_value也是一个要重点挖掘的结构,在PHP扩展开发以及内部函数使用中的返回值结构 array_init(return_value); if (str_len == 0) { if (limit >= 0) { //如果待切割字符串长度0,limit大于等于0时返回数组仅包含一个元素""空串 add_next_index_stringl(return_value, "", sizeof("") - 1, 1); } return; } //这里开始包装两个字符串到zval这个结构中 ZVAL_STRINGL(&zstr, str, str_len, 0); ZVAL_STRINGL(&zdelim, delim, delim_len, 0); if (limit > 1) { //开始分割 php_explode(&zdelim, &zstr, return_value, limit); } else if (limit < 0) { //limit -1特性的实现 php_explode_negative_limit(&zdelim, &zstr, return_value, limit); } else { //limit==0,返回的依旧是数组包含一个元素 0 add_index_stringl(return_value, 0, str, str_len, 1); } }这里有几个很重要的宏以及数据结构备注一下,后续会深入阐述。
zend_parse_parameters 用于接受PHP API函数中的输入参数
return_value PHP API中的返回值结构体(在PHP中变量类型最后都对应一个结构体,而这个结构体中的共用体部分正是实现了PHP数据类型的动态性)
explode实现的基本算法(php_explode)
基本思路就是从首指针开始往尾部移动,每次查找与分隔符的位置就把前面部分的值存入return_value(一个哈希表结构)中
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); //计算第一个分隔符的指针 p2 = php_memnstr(Z_STRVAL_P(str), Z_STRVAL_P(delim), Z_STRLEN_P(delim), endp); //如果没有分隔符则返回全部字符串 if (p2 == NULL) { add_next_index_stringl(return_value, p1, Z_STRLEN_P(str), 1); } else { //有的话开始查询分隔符的位置,循环把分隔符之间的内容拷贝到return_val这个哈希结构中 do { add_next_index_stringl(return_value, p1, p2 - p1, 1); p1 = p2 + Z_STRLEN_P(delim); //php_memnstr用于查找分隔符并返回第一个分隔符位置的函数,后面可以看一下他的实现 } while ((p2 = php_memnstr(p1, Z_STRVAL_P(delim), Z_STRLEN_P(delim), endp)) != NULL && --limit > 1); if (p1 <= endp) add_next_index_stringl(return_value, p1, endp-p1, 1); } }
php_memnstr(查找字符串(分隔符)位置的实现)
zend_memnstr(char *haystack, char *needle, int needle_len, char *end) { 字符首指针 char *p = haystack; 最后一个字符 char ne = needle[needle_len-1]; 结束字符串位置,只需到end-needle_len位置即可 end -= needle_len; while (p <= end) { 在数组的前n个字节中搜索字符 memchr(p, *needle, (end-p+1)) if ((p = (char *)memchr(p, *needle, (end-p+1))) && ne == p[needle_len-1]) { 如果找到首字节并且最后一个字节相同 if (!memcmp(needle, p, needle_len-1)) { 对比找到啦那么返回首指针 return p; } } if (p == NULL) { return NULL; } p++; } return NULL; }
explode limit 参数还支持负数,当为负数的时候调用的函数是php_explode_negative_limit,他的实现算法很简单,首先通过php_memnstr把所有分割的字符串的首地址全部记录在一个position指针数组中,并且记录数组的长度found,然后通过found+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) { /* 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[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 }
上面这段就不加注释了,读者可以自己研读一下代码,比较之前的explode的实现,其实关键的地方就是多了一个position的指针数组的实现。
- 上一篇: PHP内核每天挖一点-数组的底层结构
- 下一篇:没有了