(文中任何描述以及阐述不正确的地方希望大家不令赐教)
每天挖一点,今天挖一挖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的指针数组的实现。