PHP源碼之explode使用說(shuō)明
更新時(shí)間:2011年08月05日 01:07:07 作者:
最近一直在想有關(guān)字符串操作的一些效率上的事情,截取字串的問(wèn)題,都會(huì)避免不了重新分配空間的消耗,也順帶看了explode這個(gè)函數(shù)的源碼,理解下,拿出自己的分析共享下
當(dāng)我們需要將一個(gè)數(shù)組根據(jù)某個(gè)字符或字串進(jìn)行分割成數(shù)組的時(shí)候,explode用的很happy,但是你知道~explode是怎么工作的么~~
首先可以肯定的是,explode也是會(huì)分配空間的,毫無(wú)疑問(wèn)。
//文件1:ext/standard/string.c
//先來(lái)看下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;
}
//這里會(huì)開(kāi)辟一個(gè)數(shù)組,用來(lái)存放分割后的數(shù)據(jù)
array_init(return_value);
//因?yàn)檫@個(gè),我們用explode('|', '');成為了合法的
if (str_len == 0) {
if (limit >= 0) {
add_next_index_stringl(return_value, "", sizeof("") - 1, 1);
}
return;
}
//下面這兩個(gè)是將原字串和分割符都構(gòu)建成_zval_struct 結(jié)構(gòu),
//ZVAL_STRINGL會(huì)分配空間哦~~源代碼隨后貼出
ZVAL_STRINGL(&zstr, str, str_len, 0);
ZVAL_STRINGL(&zdelim, delim, delim_len, 0);
//limit值是explode中允許傳遞的explode的第三個(gè)參數(shù),它允許正負(fù)
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的實(shí)現(xiàn): 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也是使用了上訴的實(shí)現(xiàn)
下面根據(jù)explode的第三個(gè)參數(shù)limit來(lái)分析調(diào)用:條件對(duì)應(yīng)的是explode中最后的三行,對(duì)limit條件的不同
注: limit在缺省的時(shí)候(沒(méi)有傳遞),他的默認(rèn)值是LONG_MAX,也就是屬于分支1的情況
1、limit > 1 :
調(diào)用php_explode方法,該方法也可以在ext/standard/string.c中找到,并且是緊接著explode實(shí)現(xiàn)的上面出現(xiàn)(所以在查找本函數(shù)中調(diào)用來(lái)自本文件的方法的時(shí)候很方便,幾乎無(wú)一列外都是在該函數(shù)的緊接著的上面^_^),
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);
//記錄開(kāi)始位置
p1 = Z_STRVAL_P(str);
//下面這個(gè)是獲得分割符在str中的位置,可以看到在strrpos和strpos中也用到了這個(gè)方法去定位
p2 = php_memnstr(Z_STRVAL_P(str), Z_STRVAL_P(delim), Z_STRLEN_P(delim), endp);
if (p2 == NULL) {
//因?yàn)檫@個(gè),所以當(dāng)我們調(diào)用explode('|', 'abc');是合法的,出來(lái)的的就是array(0 => 'abc')
add_next_index_stringl(return_value, p1, Z_STRLEN_P(str), 1);
} else {
//依次循環(huán)獲得下一個(gè)分隔符的位置,直到結(jié)束
do {
//將得到的子字串(上個(gè)位置到這個(gè)位置中間的一段,第一次的時(shí)候上個(gè)位置就是開(kāi)始
add_next_index_stringl(return_value, p1, p2 - p1, 1);
//定位到分隔符位置p2+分隔符的長(zhǎng)度的位置
//比如,分隔符='|', 原字串= '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);
//將最后的一個(gè)分隔符后面的字串放到結(jié)果數(shù)組中
//explode('|', 'avc|sdf'); => array(0 => 'avc', 1= > 'sdf')
if (p1 <= endp)
add_next_index_stringl(return_value, p1, endp-p1, 1);
}
}
2、limit < 0 :
調(diào)用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) {
//它這里竟然沒(méi)有處理,那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的聲明,這個(gè)數(shù)組是用來(lái)保存所有子字串的讀取位置
positions[found++] = p1; //當(dāng)然起始位置還是需要保存
//下面兩個(gè)循環(huán),第一個(gè)是循環(huán)所有在字符串中出現(xiàn)的分隔符位置,并保存下一個(gè)子字串讀取位置起來(lái)
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);
//這個(gè)就是從數(shù)組中開(kāi)始獲得返回的結(jié)果將從哪個(gè)子字串開(kāi)始讀
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);//很重要,釋放內(nèi)存
}
#undef EXPLODE_ALLOC_STEP
}
3、limit = 1 or limit = 0 :
當(dāng)所有第一和第二條件都不滿足的時(shí)候,就進(jìn)入的這個(gè)分支,這個(gè)分支很簡(jiǎn)單就是將源字串放到輸出數(shù)組中,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
///太長(zhǎng)了~~~~不貼了
可見(jiàn)(不包含分配空間這些),
當(dāng)limit>1的時(shí)候,效率是O(N)【N為limit值】,
當(dāng)limit<0的時(shí)候,效率是O(N+M)【N為limit值, M 為分割符出現(xiàn)次數(shù)】,
當(dāng)limit=1 or limit=0 的時(shí)候, 效率是O(1)
首先可以肯定的是,explode也是會(huì)分配空間的,毫無(wú)疑問(wèn)。
復(fù)制代碼 代碼如下:
//文件1:ext/standard/string.c
//先來(lái)看下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;
}
//這里會(huì)開(kāi)辟一個(gè)數(shù)組,用來(lái)存放分割后的數(shù)據(jù)
array_init(return_value);
//因?yàn)檫@個(gè),我們用explode('|', '');成為了合法的
if (str_len == 0) {
if (limit >= 0) {
add_next_index_stringl(return_value, "", sizeof("") - 1, 1);
}
return;
}
//下面這兩個(gè)是將原字串和分割符都構(gòu)建成_zval_struct 結(jié)構(gòu),
//ZVAL_STRINGL會(huì)分配空間哦~~源代碼隨后貼出
ZVAL_STRINGL(&zstr, str, str_len, 0);
ZVAL_STRINGL(&zdelim, delim, delim_len, 0);
//limit值是explode中允許傳遞的explode的第三個(gè)參數(shù),它允許正負(fù)
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);
}
}
復(fù)制代碼 代碼如下:
//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的實(shí)現(xiàn): 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也是使用了上訴的實(shí)現(xiàn)
下面根據(jù)explode的第三個(gè)參數(shù)limit來(lái)分析調(diào)用:條件對(duì)應(yīng)的是explode中最后的三行,對(duì)limit條件的不同
注: limit在缺省的時(shí)候(沒(méi)有傳遞),他的默認(rèn)值是LONG_MAX,也就是屬于分支1的情況
1、limit > 1 :
調(diào)用php_explode方法,該方法也可以在ext/standard/string.c中找到,并且是緊接著explode實(shí)現(xiàn)的上面出現(xiàn)(所以在查找本函數(shù)中調(diào)用來(lái)自本文件的方法的時(shí)候很方便,幾乎無(wú)一列外都是在該函數(shù)的緊接著的上面^_^),
復(fù)制代碼 代碼如下:
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);
//記錄開(kāi)始位置
p1 = Z_STRVAL_P(str);
//下面這個(gè)是獲得分割符在str中的位置,可以看到在strrpos和strpos中也用到了這個(gè)方法去定位
p2 = php_memnstr(Z_STRVAL_P(str), Z_STRVAL_P(delim), Z_STRLEN_P(delim), endp);
if (p2 == NULL) {
//因?yàn)檫@個(gè),所以當(dāng)我們調(diào)用explode('|', 'abc');是合法的,出來(lái)的的就是array(0 => 'abc')
add_next_index_stringl(return_value, p1, Z_STRLEN_P(str), 1);
} else {
//依次循環(huán)獲得下一個(gè)分隔符的位置,直到結(jié)束
do {
//將得到的子字串(上個(gè)位置到這個(gè)位置中間的一段,第一次的時(shí)候上個(gè)位置就是開(kāi)始
add_next_index_stringl(return_value, p1, p2 - p1, 1);
//定位到分隔符位置p2+分隔符的長(zhǎng)度的位置
//比如,分隔符='|', 原字串= '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);
//將最后的一個(gè)分隔符后面的字串放到結(jié)果數(shù)組中
//explode('|', 'avc|sdf'); => array(0 => 'avc', 1= > 'sdf')
if (p1 <= endp)
add_next_index_stringl(return_value, p1, endp-p1, 1);
}
}
2、limit < 0 :
調(diào)用php_explode_negative_limit方法
復(fù)制代碼 代碼如下:
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) {
//它這里竟然沒(méi)有處理,那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的聲明,這個(gè)數(shù)組是用來(lái)保存所有子字串的讀取位置
positions[found++] = p1; //當(dāng)然起始位置還是需要保存
//下面兩個(gè)循環(huán),第一個(gè)是循環(huán)所有在字符串中出現(xiàn)的分隔符位置,并保存下一個(gè)子字串讀取位置起來(lái)
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);
//這個(gè)就是從數(shù)組中開(kāi)始獲得返回的結(jié)果將從哪個(gè)子字串開(kāi)始讀
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);//很重要,釋放內(nèi)存
}
#undef EXPLODE_ALLOC_STEP
}
3、limit = 1 or limit = 0 :
當(dāng)所有第一和第二條件都不滿足的時(shí)候,就進(jìn)入的這個(gè)分支,這個(gè)分支很簡(jiǎn)單就是將源字串放到輸出數(shù)組中,explode('|', 'avc|sd', 1) or explode('|', 'avc|sd', 0) 都將返回array(0 => 'avc|sd');
復(fù)制代碼 代碼如下:
//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
///太長(zhǎng)了~~~~不貼了
可見(jiàn)(不包含分配空間這些),
當(dāng)limit>1的時(shí)候,效率是O(N)【N為limit值】,
當(dāng)limit<0的時(shí)候,效率是O(N+M)【N為limit值, M 為分割符出現(xiàn)次數(shù)】,
當(dāng)limit=1 or limit=0 的時(shí)候, 效率是O(1)
相關(guān)文章
ThinkPHP開(kāi)發(fā)框架函數(shù)詳解:C方法
這篇文章主要給大家講解ThinkPHP開(kāi)發(fā)框架函數(shù)詳解:C方法,有需要的朋友可以參考下2015-08-08
PHP 強(qiáng)制性文件下載功能的函數(shù)代碼(任意文件格式)
PHP強(qiáng)制性文件下載函數(shù)代碼,使用此函數(shù)為用戶提供強(qiáng)制性的文件下載功能。2010-05-05
PHP字符串和十六進(jìn)制如何實(shí)現(xiàn)互相轉(zhuǎn)換
這篇文章主要介紹了PHP字符串和十六進(jìn)制如何實(shí)現(xiàn)互相轉(zhuǎn)換,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07
兼容性最強(qiáng)的PHP生成縮略圖的函數(shù)代碼(修改版)
寫(xiě)通用性程序考慮兼容性是很頭痛的事情,關(guān)于用PHP生成縮略圖的代碼很多,不過(guò)能完全兼容gd1.6和gd2.x,并能保證縮圖清晰性的代碼幾乎沒(méi)有,我把我以前的代碼改了一下,就能實(shí)現(xiàn)了。2011-01-01
Thinkphp結(jié)合AJAX長(zhǎng)輪詢實(shí)現(xiàn)PC與APP推送詳解
這篇文章主要給大家介紹了關(guān)于Thinkphp結(jié)合AJAX長(zhǎng)輪詢實(shí)現(xiàn)PC與APP推送的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-07-07
PHP中數(shù)字檢測(cè)is_numeric與ctype_digit的區(qū)別介紹
PHP中的兩個(gè)函數(shù)is_numeric和ctype_digit都是檢測(cè)字符串是否是數(shù)字,但也存在一點(diǎn)區(qū)別2012-10-10
php Xdebug 調(diào)試擴(kuò)展的安裝與使用.
下載xdebug擴(kuò)展的時(shí)候注意xdebug的版本與php版本相對(duì)應(yīng),不然出現(xiàn)不必要的麻煩!2010-03-03

