深入理解PHP之陣列 遍歷順序

2021-09-29 14:36:14 字數 2588 閱讀 6495

經常會有人問我, php的陣列, 如果用foreach來訪問, 遍歷的順序是固定的麼? 以什麼順序遍歷呢?

比如:

<?php

foreach ($arr as $key => $val)

又比如:

<?php

$arr[2] = 'huixinchen';

$arr[1]  = 2007;

$arr[0]  = 2008;

foreach ($arr as $key => $val)

要完全了解清楚這個問題, 我想首先應該要大家了解php陣列的內部實現結構………

在php中, 陣列是用一種hash結構(hashtable)來實現的, php使用了一些機制, 使得可以在o(1)的時間複雜度下實現陣列的增刪, 並同時支援線性遍歷和隨機訪問.

之前的文章中也討論過, php的hash演算法, 基於此, 我們做進一步的延伸.

認識hashtable之前, 首先讓我們看看hashtable的結構定義, 我加了注釋方便大家理解:

typedef struct _hashtable  

hashtable;

<?php    

$arr = array(1,2,3,4,5,);    

$arr = &$arr;     

var_export($arr); //fatal error: nesting level too deep - recursive dependency?

這個字段就是為了防治迴圈引用導致的無限迴圈而設立的.

檢視上面的結構, 可以看出, 對於hashtable, 關鍵元素就是arbuckets了, 這個是實際儲存的容器, 讓我們來看看它的結構定義:

typedef struct bucket  

bucket;

我們注意到, 最後乙個元素, 這個是flexible array技巧, 可以節省記憶體,和方便初始化的一種做法, 有興趣的朋友可以google flexible array.

h是元素的hash值,對於數字索引的元素,h為直接索引值(通過nkeylength=0來表示是數字索引).而對於字串索引來說, 索引值儲存在arkey中, 索引的長度儲存在nkeylength中.

在bucket中,實際的資料是儲存在pdata指標指向的記憶體塊中,通常這個記憶體塊是系統另外分配的。但有一種情況例外,就是當bucket儲存 的資料是乙個指標時,hashtable將不會另外請求系統分配空間來儲存這個指標,而是直接將該指標儲存到pdataptr中,然後再將pdata指向本結構成員的位址。這樣可以提高效率,減少記憶體碎片。由此我們可以看到php hashtable設計的精妙之處。如果bucket中的資料不是乙個指標,pdataptr為null(本段來自altair的」zend hashtable詳解」)

結合上面的hashtable結構, 我們來說明下hashtable的總結構圖:

hashtable的plisthhead指向線性列表形式下的第乙個元素, 上圖中是元素1, plisttail指向的是最後乙個元素0, 而對於每乙個元素plistnext就是紅色線條畫出的線性結構的下乙個元素, 而plistlast是上乙個元素.

pinternalpointer指向當前的內部指標的位置, 在對陣列進行順序遍歷的時候, 這個指標指明了當前的元素.

比如, 對於foreach, 如果我們檢視它生成的opcode序列, 我們可以發現, 在foreach之前, 會首先有個fe_reset來重置陣列的內部指標, 也就是pinternalpointer(關於foreach可以參看深入理解php原理之foreach), 然後通過每次fe_fetch來遞增pinternalpointer,從而實現順序遍歷.

類似的, 當我們使用, each/next系列函式來遍歷的時候, 也是通過移動陣列的內部指標而實現了順序遍歷, 這裡有乙個問題, 比如:

<?php

$arr = array(1,2,3,4,5);

foreach ($arr as $v)   

while (list($key, $v) = each($arr)) 

?>

了解到我剛才介紹的知識, 那麼這個問題也就很明朗了, 因為foreach會自動reset, 而while這塊不會reset, 所以在foreach結束以後, pinternalpointer指向陣列最末端, while語句塊當然訪問不到了, 解決的辦法就是在each之前, 先reset陣列的內部指標.

而在隨機訪問的時候, 就會通過hash值確定在hash陣列中的頭指標位置, 然後通過pnext/plast來找到特點元素.

huixinchen

2007

2008

所以, 如果你想在數字索引的陣列中按照索引大小遍歷, 那麼你就應該使用for, 而不是foreach

for($i=0,$l=count($arr); $i

深入理解PHP之陣列 遍歷順序

又比如 arr 2 huixinchen arr 1 2007 arr 0 2008 foreach arr as key val 要完全了解清楚這個問題,我想首先應該要大家了解php陣列的內部實現結構 php的陣列在php中,陣列是用一種hash結構 hashtable 來實現的,php使用了一些...

深入理解PHP之陣列遍歷

經常會有人問我,php的陣列,如果用foreach來訪問,遍歷的順序是固定的麼?以什麼順序遍歷呢?比如 又比如 arr 2 huixinchen arr 1 2007 arr 0 2008 foreach arr as key val 要完全了解清楚這個問題,我想首先應該要大家了解php陣列的內部實...

PHP 陣列遍歷順序理解

比如 又比如 arr 2 huixinchen arr 1 2007 arr 0 2008 foreach arr as key val 要完全了解清楚這個問題,我想首先應該要大家了解php陣列的內部實現結構 在php中,陣列是用一種hash結構 hashtable 來實現的,php使用了一些機制,...