在C語言中,我們可以自定義各種各樣的數據結構,用來把很多數據保存在一個變量里面,但是每種數據結構都有自己的優缺點,PHP內核規模如此龐大,是否已經找到了一些非常棒的解決方法呢?
我們在選擇各種數據結構時,往往會考慮我們需要處理的數據規模以及需要的性能。下面讓我們簡要的看一下看C語言中數組和鏈表的一些事情。
數組
作者這里用的不是Array,而是Vector,可能指的是C++里的Vector,它與數組幾乎是完全一樣的,唯一的不同便是可以實現動態存儲。本節下文都是用數組一詞代替之,請各位注意。數組是內存中一塊連續的區域,其每一個元素都具有一個唯一的下標值。
1 |
int a[3]; |
2 |
a[0]=1; |
3 |
a[2]=3; |
不僅是整數,其它類型的變量也可以保存在數組中,比如我們前面用到的zend_get_parameters_array_ex(),便把很多zval**類型的變量保存到一個數組里,為了使其正常工作,我們提前向系統申請了相應大小的內存空間。
1 |
zval ***args = safe_emalloc(ZEND_NUM_ARGS(), sizeof (zval**), 0); |
這里我們仍然可以用一個整數來當作下標去數組中取出我們想要的數據,就像var_dump()的實現中通過args[i]來獲取參數並把它傳遞給php_var_dump()函數那樣。
使用數組最大的好處便是速度!讀寫都可以在O(1)內完成,因為它每個元素的大小都是一致的,只要知道下標,便可以瞬間計算出其對應的元素在內存中的位置,從而直接取出或者寫入。
鏈表
鏈表也是一種經常被使用的一種數據結構。鏈表中的每一個元素都至少有兩個元素,一個指向它的下一個元素,一個用來存放它自己的數據,就像下面定義的那樣:
1 |
typedef struct _namelist namelist; |
2 |
struct |
3 |
{ |
4 |
struct _namelist *next; |
5 |
char *name; |
6 |
}_namelist; |
我們可以聲明一個其類型的元素:
1 |
static namelist *people; |
假設每一個元素都代表一個人,元素中的name屬性便是這個人的名字,我們通過這樣的語句來得到它:people->name; 第二個屬性指向后面的一個元素,那我們便可以這樣來訪問下一個人的名字:people->next->name, 或者下一個人的下一個人的名字:people->next->next->name,一次類推,直到next的值是NULL,代表結 束。
1 |
//通過一個循環來遍歷這個鏈表中的所有人~ |
2 |
void name_show(namelist *p) |
3 |
{ |
4 |
while (p) |
5 |
{ |
6 |
printf ( "Name: %s\n" , p->name); |
7 |
p = p->next; |
8 |
} |
9 |
} |
鏈表可以被用來實現FIFO模式,達到先進者先出的目的!
01 |
static namelist *people = NULL, *last_person = NULL; |
02 |
void name_add(namelist *person) |
03 |
{ |
04 |
person->next = NULL; |
05 |
if (!last_person) { |
06 |
/* No one in the list yet */ |
07 |
people = last_person = person; |
08 |
return ; |
09 |
} |
10 |
/* Append new person to the end of the list */ |
11 |
last_person->next = person; |
12 |
13 |
/* Update the list tail */ |
14 |
last_person = person; |
15 |
} |
16 |
namelist *name_pop( void ) |
17 |
{ |
18 |
namelist *first_person = people; |
19 |
if (people) { |
20 |
people = people->next; |
21 |
} |
22 |
return first_person; |
23 |
} |
這樣,我們便可以隨意的向這個鏈表中添加或者刪除數據,而不向數組那樣,謹慎的考慮是否越界等問題。
上面實現的結構的學名叫做單向鏈表,也有地方叫單鏈表,反正是比較簡單的意思~。它有一個致命的缺點,就是我們在插入或者讀取某條數據的時候,都需 要從這個鏈表的開始,一個個元素的向下尋找,直到找到這個元素為止。如果鏈表中的元素比較多,那它很容易成為我們程序中的CPU消耗大戶,進而引起性能問 題。為了解決這個問題,先人們發明了雙向鏈表:
1 |
typedef struct _namelist namelist; |
2 |
struct |
3 |
{ |
4 |
namelist *next, *prev; |
5 |
char *name; |
6 |
} _namelist; |
改動其實不大,就是在每個元素中都添加了一個prev屬性,用來指向它的上一個元素。
01 |
void name_add(namelist *person) |
02 |
{ |
03 |
person->next = NULL; |
04 |
if (!last_person) |
05 |
{ |
06 |
/* No one in the list yet */ |
07 |
people = last_person = person; |
08 |
person->prev = NULL; |
09 |
return ; |
10 |
} |
11 |
/* Append new person to the end of the list */ |
12 |
last_person ->next = person; |
13 |
person->prev = last_person; |
14 |
15 |
/* Update the list tail */ |
16 |
last_person = person; |
17 |
} |
單單通過上面的程序你還體會不到它的好處,但是設想一下,如果現在你有這個鏈表中其中一個元素的地址,並且想把它從鏈表中刪除,那我們該怎么做呢?如果是單向鏈表的話,我們只能這樣做:
01 |
void name_remove(namelist *person) |
02 |
{ |
03 |
namelist *p; |
04 |
if (person == people) { |
05 |
/* Happens to be the first person in the list */ |
06 |
people = person->next; |
07 |
if (last_person == person) { |
08 |
/* Also happens to be the last person */ |
09 |
last_person = NULL; |
10 |
} |
11 |
return ; |
12 |
} |
13 |
/* Search for prior person */ |
14 |
p = people; |
15 |
while (p) { |
16 |
if (p->next == person) { |
17 |
/* unlink */ |
18 |
p->next = person->next; |
19 |
if (last_person == person) { |
20 |
/* This was the last element */ |
21 |
last_person = p; |
22 |
} |
23 |
return ; |
24 |
} |
25 |
p = p->next; |
26 |
} |
27 |
/* Not found in list */ |
28 |
} |
現在讓我們來看看雙向鏈表是怎樣來處理這個問題的:
01 |
void name_remove(namelist *person) |
02 |
{ |
03 |
if (people == person) { |
04 |
people = person->next; |
05 |
} |
06 |
if (last_person == person) { |
07 |
last_person = person->prev; |
08 |
} |
09 |
if (person->prev) { |
10 |
11 |
person->prev->next = person->next; |
12 |
} |
13 |
if (person->next) { |
14 |
person->next->prev = person->prev; |
15 |
} |
16 |
} |
對元素的遍歷查找不見了,取而代之的是一個O(1)的運算,這將極大的提升我們程序的性能。