PHP7-數(shù)組底層解析

PHP 中的數(shù)組實(shí)際上是一個(gè)有序映射。映射是一種把 values 關(guān)聯(lián)到 keys 的類(lèi)型。--PHP手冊(cè)

數(shù)組是PHP中非常強(qiáng)大、靈活的一種數(shù)據(jù)類(lèi)型翁潘,它的底層實(shí)現(xiàn)為HashTable(哈希表),除了我們熟悉的PHP用戶空間的Array類(lèi)型之外歼争,內(nèi)核中也隨處用到哈希表拜马,比如函數(shù)渗勘、類(lèi)、常量俩莽、include文件的索引表旺坠、全局符號(hào)表等都用的HashTable存儲(chǔ)。
1扮超、 數(shù)組結(jié)構(gòu)

PHP中HashTable的數(shù)據(jù)結(jié)構(gòu)如下:

//Bucket:散列表中存儲(chǔ)的元素
typedef struct _Bucket {
 zval val; //存儲(chǔ)的具體value取刃,這里嵌入了一個(gè)zval,而不是一個(gè)指針
 zend_ulong h; //key根據(jù)times 33計(jì)算得到的哈希值出刷,或者是數(shù)值索引編號(hào)
 zend_string *key; //存儲(chǔ)元素的key
} Bucket;
//HashTable結(jié)構(gòu)
typedef struct _zend_array HashTable;
struct _zend_array {
 zend_refcounted_h gc;
 union {
 struct {
 ZEND_ENDIAN_LOHI_4(
 zend_uchar flags,
 zend_uchar nApplyCount,
 zend_uchar nIteratorsCount,
 zend_uchar reserve)
 } v;
 uint32_t flags;
 } u;
 uint32_t nTableMask; //哈希值計(jì)算掩碼璧疗,等于nTableSize的負(fù)值(nTableMask = -nTableSize)
 Bucket *arData; //存儲(chǔ)元素?cái)?shù)組,指向第一個(gè)Bucket
 uint32_t nNumUsed; //已用Bucket數(shù)
 uint32_t nNumOfElements; //哈希表有效元素?cái)?shù)
 uint32_t nTableSize; //哈希表總大小馁龟,為2的n次方
 uint32_t nInternalPointer;
 zend_long nNextFreeElement; //下一個(gè)可用的數(shù)值索引,如:arr[] = 1;arr["a"] = 2;arr[] = 3; 則nNextFreeElement = 2;
 dtor_func_t pDestructor;
};

注意:nNumUsed:已用bucket數(shù) nNumOfElements:哈希表有效元素?cái)?shù)

兩者有不同的含義崩侠,當(dāng)將一個(gè)元素從哈希表刪除時(shí)并不會(huì)將對(duì)應(yīng)的Bucket移除,而是將Bucket存儲(chǔ)的zval修改為IS_UNDEF坷檩,只有擴(kuò)容時(shí)發(fā)現(xiàn)nNumOfElements與nNumUsed相差達(dá)到一定數(shù)量時(shí)才會(huì)將已刪除的元素全部移除却音,重新構(gòu)建哈希表,所以 nNumUsed >= nNumOfElements矢炼。

HashTable中另外一個(gè)非常重要的值arData系瓢,這個(gè)值指向存儲(chǔ)元素?cái)?shù)組的第一個(gè)Bucket,插入元素時(shí)按順序依次插入數(shù)組句灌,比如第一個(gè)元素在arData[0]夷陋、第二個(gè)在arData[1] ... arData[nNumUsed]。PHP數(shù)組的有序性正是通過(guò)arData保證的胰锌。

所以肌稻,HashTable主要依賴arData實(shí)現(xiàn)元素的存儲(chǔ)、索引匕荸。插入一個(gè)元素時(shí)先將元素按先后順序插入Bucket數(shù)組,位置是idx枷邪,再根據(jù)key的哈希值映射到散列表中的某個(gè)位置nIndex榛搔,將idx存入這個(gè)位置;查找時(shí)先在哈希表中映射到nIndex东揣,得到value在Bucket數(shù)組的位置idx践惑,再?gòu)腂ucket數(shù)組中取出元素。

2嘶卧、 哈希碰撞

哈希碰撞是指不同的key可能計(jì)算得到相同的哈希值(數(shù)值索引的哈希值直接就是數(shù)值本身)尔觉,但是這些值又需要插入同一個(gè)哈希表。

出現(xiàn)哈希碰撞之后芥吟,又如何解決呢侦铜?一般有如下幾種方法:

鏈表法:為每個(gè) Hash 值建立一個(gè)單鏈表专甩,當(dāng)發(fā)生沖突時(shí),將記錄插入到鏈表中钉稍。
開(kāi)放尋址法:有一個(gè)公式:Hi=(H(key)+di) MOD m i=1,2,…,k(k<=m-1),基本思想:當(dāng)發(fā)生地址沖突時(shí)涤躲,按照某種方法繼續(xù)探測(cè)哈希表中的其他存儲(chǔ)單元,直到找到空位置為止贡未。
再哈希法:當(dāng)發(fā)生沖突時(shí)种樱,使用第二個(gè)、第三個(gè)哈希函數(shù)計(jì)算地址俊卤,直到無(wú)沖突時(shí)嫩挤。

3、 擴(kuò)容

哈希表可存儲(chǔ)的value數(shù)是固定的消恍,當(dāng)空間不夠用時(shí)就要進(jìn)行擴(kuò)容了岂昭。

PHP哈希表的大小為2^n,插入時(shí)如果容量不夠則首先檢查已刪除元素所占比例哺哼,如果達(dá)到閾值時(shí)佩抹,則將已刪除元素移除,重建索引取董;如果未到閾值則進(jìn)行擴(kuò)容操作棍苹,擴(kuò)大為當(dāng)前大小的2倍,將當(dāng)前Bucket數(shù)組復(fù)制到新的空間茵汰,然后重建索引枢里。

4、重建散列表

當(dāng)刪除元素達(dá)到一定數(shù)量或擴(kuò)容后就需要重建哈希表蹂午,因?yàn)関alue在Bucket位置移動(dòng)了或哈希數(shù)組nTableSize變化了導(dǎo)致key與value的映射關(guān)系改變栏豺。

重建過(guò)程實(shí)際就是遍歷Bucket數(shù)組中的value,然后重新計(jì)算映射值更新到哈希表豆胸。

此外奥洼,還有一個(gè)重要的處理:移除已刪除的value,開(kāi)始的時(shí)候我們說(shuō)過(guò)晚胡,刪除value時(shí)只是將value的type設(shè)置為IS_UNDEF灵奖,并沒(méi)有實(shí)際從Bucket數(shù)組中刪除,如果這些value一直存在那么將浪費(fèi)很多空間估盘,所以這里會(huì)把它們移除瓷患,操作的方式也比較簡(jiǎn)單:將后面未刪除的value依次前移。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末遣妥,一起剝皮案震驚了整個(gè)濱河市擅编,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖爱态,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谭贪,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡肢藐,警方通過(guò)查閱死者的電腦和手機(jī)故河,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)吆豹,“玉大人鱼的,你說(shuō)我怎么就攤上這事《幻海” “怎么了凑阶?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)衷快。 經(jīng)常有香客問(wèn)我宙橱,道長(zhǎng),這世上最難降的妖魔是什么蘸拔? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任师郑,我火速辦了婚禮,結(jié)果婚禮上调窍,老公的妹妹穿的比我還像新娘宝冕。我一直安慰自己,他們只是感情好邓萨,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布地梨。 她就那樣靜靜地躺著,像睡著了一般缔恳。 火紅的嫁衣襯著肌膚如雪宝剖。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,829評(píng)論 1 290
  • 那天歉甚,我揣著相機(jī)與錄音万细,去河邊找鬼。 笑死纸泄,一個(gè)胖子當(dāng)著我的面吹牛赖钞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播刃滓,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼耸弄!你這毒婦竟也來(lái)了咧虎?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤计呈,失蹤者是張志新(化名)和其女友劉穎砰诵,沒(méi)想到半個(gè)月后征唬,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡茁彭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年总寒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片理肺。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡摄闸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出妹萨,到底是詐尸還是另有隱情年枕,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布乎完,位于F島的核電站熏兄,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏树姨。R本人自食惡果不足惜摩桶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望帽揪。 院中可真熱鬧硝清,春花似錦、人聲如沸台丛。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)挽霉。三九已至防嗡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間侠坎,已是汗流浹背蚁趁。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留实胸,地道東北人他嫡。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像庐完,于是被迫代替她去往敵國(guó)和親钢属。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349