在 PHP 中數(shù)組是使用最多的數(shù)據(jù)類(lèi)型.大多數(shù)時(shí)間,我們不需要考慮 PHP 數(shù)組在編碼中或者應(yīng)用中的作用.我們喜歡 PHP 數(shù)組的動(dòng)態(tài)特性,有時(shí)我們甚至不想去探究是否可以使用除數(shù)組意外的數(shù)據(jù)類(lèi)型來(lái)解決我們的問(wèn)題.下面我們來(lái)探討一下 PHP 數(shù)組的優(yōu)缺點(diǎn),同時(shí)研究如何用不同的數(shù)據(jù)類(lèi)型實(shí)現(xiàn)來(lái)使用數(shù)組實(shí)現(xiàn)性能的提升.我們先來(lái)解釋 PHP 中數(shù)組的不同類(lèi)型.然后來(lái)分析一下PHP 數(shù)組元素的內(nèi)存空間以及如何通過(guò)其他數(shù)據(jù)結(jié)構(gòu)來(lái)改善性能
更好的理解 PHP 數(shù)組
PHP 中的數(shù)組實(shí)際上是一個(gè)有序映射。映射是一種把 values 關(guān)聯(lián)到 keys 的類(lèi)型。此類(lèi)型在很多方面做了優(yōu)化,因此可以把它當(dāng)成真正的數(shù)組,或列表(向量),散列表(是映射的一種實(shí)現(xiàn)),字典,集合,棧,隊(duì)列以及更多可能性。由于數(shù)組元素的值也可以是另一個(gè)數(shù)組,樹(shù)形結(jié)構(gòu)和多維數(shù)組也是允許的。
PHP數(shù)組是動(dòng)態(tài)靈活的,我們不需要去關(guān)注它是否像其他編程語(yǔ)言一樣是一個(gè)有規(guī)則的,關(guān)聯(lián)的,多維的數(shù)組.我們也不需要去定義數(shù)組的容量和數(shù)據(jù)類(lèi)型.那 PHP 是這么實(shí)現(xiàn)這些的呢?答案很簡(jiǎn)單:PHP 數(shù)組的概念不僅僅是一個(gè)真正的數(shù)組而是一個(gè) Hash-Map.
下面是數(shù)組的三種主要的類(lèi)型:
- 數(shù)字型
- 關(guān)系型
- 多維數(shù)組
數(shù)字型數(shù)組
數(shù)字型數(shù)組不僅意味著它能保存數(shù)字?jǐn)?shù)據(jù),事實(shí)上,它意味著數(shù)組額索引只能是數(shù)字.
在 PHP 中他們既可以是有序的也可以是無(wú)序的,但是他們必須是數(shù)字.在數(shù)字?jǐn)?shù)組中,值以線性的方式存儲(chǔ)和訪問(wèn).下面舉個(gè)列子
$array = [10,20,30,40,50];
$array[] = 70;
$array[] = 80;
$arraySize = count($array);
for($i = 0;$i<$arraySize;$i++) {
echo "Position ".$i." holds the value ".$array[$i]."\n";
}
Position 0 holds the value 10
Position 1 holds the value 20
Position 2 holds the value 30
Position 3 holds the value 40
Position 4 holds the value 50
Position 5 holds the value 70
Position 6 holds the value 80
上面是一個(gè)簡(jiǎn)單的數(shù)字型數(shù)組的實(shí)現(xiàn),我們可以看到當(dāng)我們添加通過(guò) $array[] 在數(shù)組中添加一個(gè)新的元素,數(shù)字自動(dòng)增加索引并且在新的索引上賦值.
如果我的數(shù)組是順序的,我們可以使用 for 進(jìn)行遍歷.
一個(gè)大的問(wèn)題的是,如果索引不是順序的,難道我們不能描述數(shù)組了嗎? 當(dāng)然是可以的,看下面的例子
$array = [];
$array[10] = 100;
$array[21] = 200;
$array[29] = 300;
$array[500] = 1000;
$array[1001] = 10000;
$array[71] = 1971;
foreach($array as $index => $value) {
echo "Position ".$index." holds the value ".$value."<br/>";
}
Position 10 holds the value 100
Position 21 holds the value 200
Position 29 holds the value 300
Position 500 holds the value 1000
Position 1001 holds the value 10000
Position 71 holds the value 1971
關(guān)系型數(shù)組
關(guān)系型數(shù)組通過(guò)鍵(可以是任意字符串)來(lái)存取數(shù)據(jù).在關(guān)系型數(shù)組中,通過(guò)鍵取代線性索引來(lái)存儲(chǔ)值.和數(shù)字?jǐn)?shù)組一樣,我們可以通過(guò)關(guān)系型數(shù)組來(lái)存儲(chǔ)任意類(lèi)型的數(shù)據(jù).
下面來(lái)看一組學(xué)生信息的例子:
$studentInfo = [];
$studentInfo['Name'] = "Adiyan";
$studentInfo['Age'] = 11;
$studentInfo['Class'] = 6;
$studentInfo['RollNumber'] = 71;
$studentInfo['Contact'] = "info@adiyan.com";
foreach($studentInfo as $key => $value) {
echo $key.": ".$value."\n";
}
輸出
Name: Adiyan
Age: 11
Class: 6
RollNumber: 71
Contact: info@adiyan.com
這里我們使用每一個(gè)鍵來(lái)保存數(shù)據(jù).我們可以根據(jù)我們的需求添加鍵.我們利用 PHP 數(shù)組靈活的展示類(lèi)似于結(jié)構(gòu)體,map, 字典這些數(shù)據(jù)結(jié)構(gòu).
多維數(shù)組
顧名思義,多維數(shù)組就是存儲(chǔ)了多個(gè)數(shù)組.換一句話說(shuō),它是存儲(chǔ)數(shù)組的數(shù)組.這里將會(huì)在不同的例子中使用多維數(shù)組
使用多維數(shù)組來(lái)表示數(shù)據(jù)結(jié)構(gòu)
在接下來(lái)的內(nèi)容中,我們將會(huì)討論不同的數(shù)據(jù)結(jié)構(gòu)和算法.一個(gè)鍵的數(shù)據(jù)結(jié)構(gòu):圖(graph).大多數(shù)時(shí)候我們都將使用 PHP 多維數(shù)組來(lái)表示鄰接矩陣
接下來(lái)分析一下下面的這個(gè)圖
[圖片上傳失敗...(image-e97294-1511863276604)]
現(xiàn)在如果我們認(rèn)為圖的每一個(gè)節(jié)點(diǎn)是數(shù)組的一個(gè)值,我們可以這樣描述節(jié)點(diǎn)
$node = ['A','B','C','D','E'];
但是上面的表達(dá)式無(wú)法表示節(jié)點(diǎn)間的連接關(guān)系,因此我們需要設(shè)計(jì)一個(gè)二維數(shù)組,這個(gè)二維數(shù)組 keys 表示names, 基于兩個(gè)節(jié)點(diǎn)的內(nèi)聯(lián) values 為0或者1.因?yàn)樘峁┑膱D沒(méi)設(shè)定方向所以我們假設(shè)這里是雙向連接.
首先,我們需要為圖創(chuàng)建一個(gè)數(shù)組,并且初始化二維數(shù)組的每個(gè)節(jié)點(diǎn)的值是0.代碼如下:
$graph = [];
$nodes = ['A', 'B', 'C', 'D', 'E'];
foreach ($nodes as $xNode) {
foreach ($nodes as $yNode) {
$graph[$xNode][$yNode] = 0;
}
}
打印 *$graph
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
現(xiàn)在我們將這樣定義節(jié)點(diǎn)間的連接,每?jī)蓚€(gè)節(jié)點(diǎn)相連將會(huì)被表示為1:
$graph["A"]["B"] = 1;
$graph["B"]["A"] = 1;
$graph["A"]["C"] = 1;
$graph["C"]["A"] = 1;
$graph["A"]["E"] = 1;
$graph["E"]["A"] = 1;
$graph["B"]["E"] = 1;
$graph["E"]["B"] = 1;
$graph["B"]["D"] = 1;
$graph["D"]["B"] = 1;
因?yàn)閳D譜中是無(wú)方向的,我們將認(rèn)為是無(wú)方向的樹(shù),因此我們對(duì)于每一個(gè)連接都設(shè)置為1.后面我們會(huì)了解到為什么我這做.現(xiàn)在我們繼續(xù)打印 *$graph:
0 1 1 0 1
1 0 0 1 1
1 0 0 0 0
0 1 0 0 0
1 1 0 0 0
好了,在后續(xù)關(guān)于圖的實(shí)際操作的文章中我們將繼續(xù)探討這個(gè)數(shù)據(jù)結(jié)構(gòu).
利用 SplFixedArray 生成固定長(zhǎng)度的數(shù)組
聊了這么多,我們已經(jīng)對(duì) PHP 數(shù)組有了很深入的了解了,但是我們從沒(méi)有定義過(guò)數(shù)組的長(zhǎng)度.PHP 數(shù)組長(zhǎng)度可以根據(jù)我們的命令增長(zhǎng)或者縮小.這一靈活性來(lái)自于強(qiáng)大的內(nèi)存管理.我們下面就來(lái)探討這些內(nèi)容,來(lái)看看使用 PHP SPL 來(lái)生成固定長(zhǎng)度的 PHP 數(shù)組.
為什么我們不生成固定長(zhǎng)度的 PHP 數(shù)組呢?難道沒(méi)有任何好處嗎?答案是:當(dāng)我們僅僅需要數(shù)組中某一個(gè)數(shù)字的時(shí)候,我們使用固定長(zhǎng)度的數(shù)組可以減少內(nèi)存的使用.在進(jìn)行內(nèi)存分析的之前,讓我們使用 SplFixedArray 舉個(gè)例子:
$array = new \SplFixedArray(10);
for ($i = 0; $i < 10; $I++)
$array[$i] = $I;
for ($i = 0; $i < 10; $I++)
echo $array[$i] . "\n";
輸出結(jié)果為0-9,當(dāng)我訪問(wèn)超過(guò)10的索引將會(huì)報(bào)錯(cuò).
PHP Fatal error: Uncaught RuntimeException: Index invalid or out of range
PHP數(shù)組和SplFixedArray的基本區(qū)別為:
- SplFixedArray必須有有限固定的長(zhǎng)度
- SplFixedArray的索引必須為integers且在0-n 范圍之內(nèi), n 為我們定義的數(shù)組長(zhǎng)度
當(dāng)我們有很多有限固定長(zhǎng)度數(shù)組或者有數(shù)組長(zhǎng)度限制的時(shí)候 SplFixedArray 將會(huì)很好用.
常規(guī) PHP 數(shù)組和 SplFixedArray 性能比較
在上一節(jié)中我們認(rèn)為的一個(gè)關(guān)鍵的問(wèn)題:為什么我們要要使用 SplFixedArray 取代傳統(tǒng) PHP 數(shù)組?通過(guò)對(duì) PHP 數(shù)組的概念的理解,PHP 數(shù)組不僅僅是數(shù)組,還是 hash maps. 下面來(lái)運(yùn)行一個(gè)例子來(lái)看一下 PHP 5.x 中 PHP 數(shù)組內(nèi)存使用
創(chuàng)建一個(gè)100000唯一的整數(shù)數(shù)組. 每一個(gè)整數(shù)占用8 bytes,所以會(huì)消耗800000 bytes
$startMemory = memory_get_usage();
$array = range(1,100000);
$endMemory = memory_get_usage();
echo ($endMemory - $startMemory)." bytes";
輸出
14649032 bytes
我們可以看見(jiàn)結(jié)果是 14649032 bytes,幾乎是設(shè)想的18.5倍.意味著數(shù)組中的每一個(gè)元素超過(guò)144 bytes (18 * 8 bytes). 那么,額外的144 bytes來(lái)自哪里?為什么 PHP 沒(méi)有利用這額外的內(nèi)存? 這里有一個(gè)關(guān)于PHP數(shù)組額外內(nèi)存占用的解釋:
[圖片上傳失敗...(image-116276-1511863276604)]
這幅圖展示了 PHP 數(shù)組內(nèi)部是如何工作的.PHP 數(shù)組存儲(chǔ)將數(shù)據(jù)存儲(chǔ)在bucket中.為了管理這個(gè)動(dòng)態(tài)特性,這里可以看到這里為數(shù)組實(shí)現(xiàn)了雙向鏈表和 hash 表.結(jié)果就導(dǎo)致耗費(fèi)大量額外的內(nèi)存.
在 PHP 7中 PHP 數(shù)組得到了優(yōu)化:
$array = Range(1,10000) | 32 bit | 64 bit |
---|---|---|
PHP 5.6 or below | 7.4MB | 14MB |
PHP 7 | 3 MB | 4 MB |
在 PHP7中對(duì)64位系統(tǒng)數(shù)組優(yōu)化了3.5倍,下面來(lái)看看 SplFixedArray:
$items = 100000;
$startMemory = memory_get_usage();
$array = new \SplFixedArray($items);
for ($i = 0; $i < $items; $i++) {
$array[$i] = $I;
}
$endMemory = memory_get_usage();
$memoryConsumed = ($endMemory - $startMemory) / (1024*1024);
$memoryConsumed = ceil($memoryConsumed);
echo "memory = {$memoryConsumed} MB\n";
memory = 2 MB
100000 條數(shù)據(jù) | 使用 PHP 數(shù)組 | 64 bit |
---|---|---|
PHP 5.6 or below | 14MB | 6 MB |
PHP 7 | 5 MB | 2 MB |
不僅在內(nèi)存使用率上,SplFixedArray而且在進(jìn)行數(shù)組比較,取值,賦值上都更快.
更多使用 SplFixedArray 的例子
既然 SplFixedArray 在性能方面有更好的表現(xiàn),我們盡量在數(shù)據(jù)類(lèi)型和算法中使用它.
將 PHP 數(shù)組轉(zhuǎn)化為 SplFixedArray
我們已經(jīng)知道如何創(chuàng)建一個(gè)定長(zhǎng)的SplFixedArray,那么怎么實(shí)時(shí)的將一個(gè)普通 PHP 數(shù)組轉(zhuǎn)化為SplFixedArray?
$array =[1 => 10, 2 => 100, 3 => 1000, 4 => 10000];
$splArray = SplFixedArray::fromArray($array);
print_r($splArray);
SplFixedArray Object
(
[0] =>
[1] => 10
[2] => 100
[3] => 1000
[4] => 10000
)
當(dāng)我們想要實(shí)時(shí)的將一個(gè)數(shù)組轉(zhuǎn)化為一個(gè)定長(zhǎng)的數(shù)組,如果我們后面不再使用常規(guī)數(shù)組,最好 unset 常規(guī)數(shù)組,這樣會(huì)最大限度的節(jié)省內(nèi)存
將 SplFixedArray 轉(zhuǎn)化為 PHP 數(shù)組
我需要將 SplFixedArray 轉(zhuǎn)化為 PHP 數(shù)組來(lái)使用很多系統(tǒng)預(yù)定義的 array 函數(shù).
$items = 5;
$array = new \SplFixedArray($items);
for ($i = 0; $i < $items; $i++) {
$array[$i] = $i * 10;
}
$newArray = $array->toArray();
print_r($newArray);
Array
(
[0] => 0
[1] => 10
[2] => 20
[3] => 30
[4] => 40
)
SplFixedArray 定義后改變長(zhǎng)度
$items = 5;
$array = new \SplFixedArray($items);
for ($i = 0; $i < $items; $i++) {
$array[$i] = $i * 10;
}
$array->setSize(10);
$array[7] = 100;
使用 SplFixedArray 生成多維數(shù)組
$array = new \SplFixedArray(100);
for ($i = 0; $i < 100; $i++)
$array[$i] = new \SplFixedArray(100);