Vysvetlite tieto hašovacie tabuľky v PHP

Explain Those Hash Tables Php



http://www.php.cn/php-weizijiaocheng-363401.html

Jednou z najdôležitejších dátových štruktúr v jadre PHP je HashTable. Pole, ktoré používame, je v jadre implementované pomocou HashTable. Ako sa teda implementuje HashTable v PHP? Nedávno som sa pozrel na dátovú štruktúru HashTable, ale v knihe algoritmov nie je žiadny konkrétny implementačný algoritmus. Práve som si nedávno prečítal zdrojový kód PHP, takže som implementoval implementáciu HashTable do jazyka PHP. Zjednodušená verzia HashTable zhŕňa niektoré z nápadov, poďme ich zdieľať pre všetkých.



Mám na github jednoduchú verziu implementácie HashTable: implementácia HashTable



Okrem toho mám podrobnejšiu anotáciu zdrojového kódu PHP na github. Záujemcovia sa môžu rozhliadnuť okolo seba, dať hviezdičku. Poznámky k zdroju PHP5.4. Pridané anotácie môžete zobraziť prostredníctvom záznamu o potvrdení.



Úvod do tabuľky HashTable

Hašovacia tabuľka je efektívna dátová štruktúra na implementáciu slovníkových operácií.

definícia



Jednoducho povedané, HashTable je dátová štruktúra párov kľúč - hodnota. Podpora vkladania, hľadania, mazania a ďalších operácií. Za určitých rozumných predpokladov je časová zložitosť všetkých operácií v hashovacej tabuľke O (1) (môžete sa na ňu odvolať sami, ak vás zaujíma dôkaz).

Kľúč na implementáciu hašovacej tabuľky

V hashovacej tabuľke sa namiesto použitia kľúčových slov na indexovanie používa funkcia hash na výpočet hašovacej hodnoty kľúča ako dolného indexu a potom sa po vyhľadaní / odstránení vypočíta hašovacia hodnota kľúča, čím sa element rýchlo nájde . s pozícia.

V hashovacej tabuľke môžu rôzne kľúčové slová vypočítať rovnakú hashovaciu hodnotu. Toto sa nazýva „hash konflikt“, čo je prípad, keď sú hash hodnoty dvoch alebo viacerých kľúčov rovnaké. Existuje mnoho spôsobov, ako vyriešiť konflikt hash, otvorené adresovanie, zips atď.

Kľúčom k implementácii dobrej hašovacej tabuľky je preto dobrá hašovacia funkcia a spôsob riešenia hašovacích kolízií.

Funkcia hash

Existujú štyri definície pre posúdenie, či je hashový algoritmus dobrý alebo zlý:> * Konzistencia, ekvivalentné kľúče musia produkovať rovnaké hash hodnoty> * Efektívne, ľahko vypočítateľné> * Uniformita, rovnomerne pre všetky kľúče Hash.

Funkcia hash ustanovuje korešpondenciu medzi kľúčovými hodnotami a hashovými hodnotami, konkrétne: h = hash_func (kľúč). Zodpovedajúci vzťah je znázornený na obrázku nižšie:

Ak chcete navrhnúť dokonalú hashovaciu funkciu, nechajte to urobiť odborníkov. Použili sme iba existujúcu zrelú hashovaciu funkciu. Hašovacia funkcia používaná jadrom PHP je funkcia time33, známa tiež ako DJBX33A, ktorá je implementovaná nasledovne:

1

dva

3

4

5

6

7

8

9

10

jedenásť

12

13

14

pätnásť

16

17

18

19

dvadsať

dvadsaťjeden

22

2. 3

24

25

26

27

28

29

static inline ulong zend_inline_hash_func(const char *arKey, uint nKeyLength)

{

register ulong hash = 5381

/* variant with the hash unrolled eight times */

for ( nKeyLength >= 8 nKeyLength -= 8) {

hash = ((hash << 5) + hash) + *arKey++

hash = ((hash << 5) + hash) + *arKey++

hash = ((hash << 5) + hash) + *arKey++

hash = ((hash << 5) + hash) + *arKey++

hash = ((hash << 5) + hash) + *arKey++

hash = ((hash << 5) + hash) + *arKey++

hash = ((hash << 5) + hash) + *arKey++

hash = ((hash << 5) + hash) + *arKey++

}

switch (nKeyLength) {

case 7: hash = ((hash << 5) + hash) + *arKey++ /* fallthrough... */

case 6: hash = ((hash << 5) + hash) + *arKey++ /* fallthrough... */

case 5: hash = ((hash << 5) + hash) + *arKey++ /* fallthrough... */

case 4: hash = ((hash << 5) + hash) + *arKey++ /* fallthrough... */

case 3: hash = ((hash << 5) + hash) + *arKey++ /* fallthrough... */

case 2: hash = ((hash << 5) + hash) + *arKey++ /* fallthrough... */

case 1: hash = ((hash << 5) + hash) + *arKey++ break

case 0: break

EMPTY_SWITCH_DEFAULT_CASE()

}

return hash

}

Poznámka: Na dosiahnutie tejto funkcie používa prepínač 8 slučiek +, ide o optimalizáciu slučky for, zníženie počtu spustení slučky a vykonanie zvyšných prvkov, ktoré v prepínači neprechádzajú.

Metóda na zips

Metóda ukladania všetkých prvkov s rovnakou hodnotou hash do prepojeného zoznamu sa nazýva metóda zipsu. Pri vyhľadávaní sa najskôr vypočíta hash hodnota zodpovedajúca kľúču, potom sa nájde zodpovedajúci prepojený zoznam podľa hash hodnoty a nakoniec sa vyhľadá zodpovedajúca hodnota v poradí prepojeného zoznamu. Štruktúrny diagram po špecifickom uchovaní je nasledovný:

Štruktúra HashTable PHP

Po krátkom úvode do dátovej štruktúry hašovacej tabuľky si môžete pozrieť, ako je hašovacia tabuľka implementovaná v PHP.

(Obrázok zo siete, porušenie je odstránené)

Definícia hashtable jadra PHP:

1

dva

3

4

5

6

7

8

9

10

jedenásť

12

13

14

pätnásť

16

17

typedef struct _hashtable {

uint nTableSize

uint nTableMask

uint nNumOfElements

ulong nNextFreeElement

Bucket *pInternalPointer

Bucket *pListHead

Bucket *pListTail

Bucket **arBuckets

dtor_func_t pDestructor

zend_bool persistent

unsigned char nApplyCount

zend_bool bApplyProtection

#if ZEND_DEBUG

int inconsistent

#endif

} HashTable

nTableSize, veľkosť tabuľky HashTable, ktorá rastie na násobky 2

nTableMask, ktorá sa používa na vykonanie operácie AND s hashovou hodnotou na získanie indexovej hodnoty hash hodnoty, arBuckets je po inicializácii vždy nTableSize-1.

nNumOfElements, počet prvkov, ktoré momentálne vlastní HashTable, funkcia count vráti túto hodnotu priamo.

nNextFreeElement, ktorý predstavuje pozíciu nasledujúceho číselného indexu v poli hodnôt číselných kľúčov

pInternalPointer, interný ukazovateľ na aktuálneho člena, používaný na prechod elementom

pListHead, ktorý ukazuje na prvý prvok tabuľky HashTable a je prvým prvkom poľa

pListTail, ktorý ukazuje na posledný prvok tabuľky HashTable a je posledným prvkom poľa. V kombinácii s vyššie uvedenými ukazovateľmi je to veľmi výhodné pri prechádzaní poľami, ako sú reset a endAPI

arBuckets, pole zoznamov dvakrát prepojených pozostávajúcich z segmentov. Index je generovaný hashom kľúča a nTableMask.

pDestructor, deštruktor používaný na mazanie prvkov v hashovacej tabuľke

Trvalo, identifikujte funkciu alokácie pamäte, ak je TRUE, použite funkciu alokácie pamäte samotného operačného systému, inak použite funkciu alokácie pamäte PHP

nApplyCount, uloží počet rekurzívnych prístupov k aktuálnemu vedru, čím zabráni viacnásobnej rekurzii

bApplyProtection, na identifikáciu toho, či má hash tabuľka používať rekurzívnu ochranu, je predvolená hodnota 1, aby sa použila

Uveďte príklad hodnoty hash kombinovanej s maskou:

Napríklad skutočný hash výrazu „foo“ (pomocou hashovacej funkcie DJBX33A) je 193491849. Ak máme teraz hash tabuľku so 64 viečkami, zjavne ju nemôžeme použiť ako dolný index poľa. Namiesto toho sa použije maska ​​hašovacej tabuľky a potom sa odoberú iba nízke bity hašovacej tabuľky.

1

dva

3

4

hash | 193491849 | 0b1011100010000111001110001001

& mask | & 63 | & 0b0000000000000000000000111111

----------------------------------------------------------------------

= index | = 9 | = 0b0000000000000000000000001001

Preto je v hashovacej tabuľke foo uložený vo vektore segmentu s dolným indexom 9 v arBuckets.

Definícia štruktúry vedra

1

dva

3

4

5

6

7

8

9

10

jedenásť

typedef struct bucket {

ulong h

uint nKeyLength

void *pData

void *pDataPtr

struct bucket *pListNext

struct bucket *pListLast

struct bucket *pNext

struct bucket *pLast

const char *arKey

} Bucket

h, hodnota hash (alebo kláves s hodnotou číselného klávesu)

nKeyLength, dĺžka kľúča

pData, ukazovateľ na dáta

pDataPtr, údaje ukazovateľa

pListNext, smerujúci na nasledujúci prvok v zozname arBuckets v HashTable

pListLast, ukazujúci na predchádzajúci prvok v zozname arBuckets v HashTable

pNext, smerujúci na ďalší prvok v zozname segmentov s rovnakou hodnotou hash

pLast, ukazuje na predchádzajúci prvok v zozname segmentov s rovnakou hodnotou hash

arKey, názov kľúča

HashTable v PHP prijíma implementáciu vektora plus dvojnásobne prepojeného zoznamu. Vektor je uložený v premennej arBuckets. Vektor obsahuje ukazovatele na viac segmentov. Každý ukazovateľ smeruje na dvojnásobne prepojený zoznam zložený z viacerých segmentov. Nový prvok sa pridá pred použitím. , to znamená, že nový prvok je vždy v prvej polohe segmentu. Ako vidíte z vyššie uvedeného, ​​implementácia hash tabuľky PHP je dosť komplikovaná. Toto je cena, ktorú musí platiť za použitie ultra flexibilných typov polí.

Príklad tabuľky HashTable v PHP je uvedený nižšie:

Rozhranie API súvisiace s HashTable

zend_hash_init

zend_hash_add_or_update

zend_hash_find

zend_hash_del_key_or_index

zend_hash_init

Krok vykonania funkcie

Nastavte veľkosť tabuľky hash

Nastavte začiatočnú hodnotu ďalších členských premenných štruktúry (vrátane deštruktora pDescructor na uvoľnenie pamäte)

Komentáre k podrobnému kódu kliknite: zdroj zend_hash_init

Poznámka:

1, funkcia pHashFunction sa tu nepoužíva, funkcia php hash používa internú zend_inline_hash_func

2, po vykonaní zend_hash_init skutočne nepridelí pamäť pre arBuckets a nevypočíta veľkosť nTableMask. Skutočné pridelenie pamäte a výpočet nTableMask sa vykoná, keď sa pri vkladaní prvkov vykoná inicializácia kontroly CHECK_INIT.

zend_hash_add_or_update

Krok vykonania funkcie

Skontrolujte dĺžku kľúča

Skontrolujte inicializáciu

Vypočítajte hash a dolný index

Prejdením segmentu, kde sa nachádza hodnota hash, ak sa nájde rovnaký kľúč a je potrebné aktualizovať hodnotu, aktualizujú sa údaje, inak bude pokračovať v ukazovaní na nasledujúci prvok segmentu, kým nebude ukazovať na poslednú pozíciu parametra vedro.

Priraďte segment k novo pridanému prvku, nastavte hodnotu vlastnosti nového segmentu a potom ho pridajte do tabuľky hash.

Ak je hašovací tabuľkový priestor plný, zmeňte veľkosť hashovacej tabuľky

Vývojový diagram vykonávania funkcií

CONNECT_TO_BUCKET_DLLIST je pridať nové prvky do zoznamu segmentov s rovnakou hodnotou hash.

CONNECT_TO_GLOBAL_DLLIST je dvojnásobne prepojený zoznam, ktorý pridáva nové prvky do tabuľky HashTable.

Podrobný kód a komentáre získate kliknutím na anotáciu kódu zend_hash_add_or_update.

zend_hash_find

Krok vykonania funkcie

Vypočítajte hash a dolný index

Traverzom po segmente, kde je umiestnená hash hodnota, ak nájde segment, kde je umiestnený kľúč, vráti hodnotu. V opačnom prípade ukazuje na ďalší segment, kým neukazuje na poslednú pozíciu v zozname segmentov.

Podrobný kód a komentáre získate kliknutím na anotáciu kódu zend_hash_find.

zend_hash_del_key_or_index

Krok vykonania funkcie

Vypočítajte hash a dolný index kľúča

Posuňte vedro tam, kde je umiestnená hodnota hash. Ak sa nájde vedro, kde sa nachádza kľúč, pokračujte tretím krokom. V opačnom prípade ukážte na nasledujúci segment, kým nebude ukazovať na poslednú pozíciu v zozname segmentov.

Ak chcete vymazať prvý prvok, priamo namierte arBucket [nIndex] na druhý prvok, zvyšok operácie je vykonať aktuálny nasledujúci z posledného nasledujúceho aktuálneho ukazovateľa

Upravte súvisiace ukazovatele

Voľná ​​dátová pamäť a pamäť štruktúry vedra

Podrobný kód a komentáre získate kliknutím na anotáciu kódu zend_hash_del_key_or_index.

Analýza výkonu

Výhody hashovacej tabuľky PHP: HashTable PHP poskytuje veľké pohodlie pre operácie s poľami. Či už je to vytváranie poľa alebo pridávanie prvkov alebo mazanie prvkov, hašovacia tabuľka poskytuje dobrý výkon, ale jej nedostatočnosť, keď je množstvo údajov veľké, je zrejmé z časovej a priestorovej zložitosti.

Nestačí takto:

Štruktúra zval, ktorá uchováva údaje, musí alokovať pamäť zvlášť. Túto nadbytočnú pamäť musíte spravovať. Každý zval zaberá 16 bajtov pamäte.

Pri pridávaní segmentu je segment tiež zvláštnym pridelením a vyžaduje tiež 16 bajtov pamäte.

Aby bolo možné vykonať postupný prechod, používa sa na prepojenie celej tabuľky HashTable obojsmerne prepojený zoznam a používa sa veľa ukazovateľov a každý ukazovateľ má tiež 16 bajtov pamäte

Ak sa pri prechode prvok nachádza na konci zoznamu segmentov, musíte tiež prejsť celým zoznamom segmentov, aby ste našli hľadanú hodnotu.

Nedostatky HashTable v PHP spočívajú hlavne v tom, že ukazovatele obojsmerne prepojeného zoznamu a zval a bucket musia alokovať ďalšiu pamäť, čo vedie k veľkému pamäťovému priestoru a veľkej časovej náročnosti pri vyhľadávaní.

Nasleduj

Vyššie uvedené nedostatky sú v PHP7 dobre vyriešené. PHP7 vykonal veľkú transformáciu dátovej štruktúry v jadre, vďaka čomu je PHP oveľa efektívnejšie. Preto sa odporúča, aby vývojári PHP vyvíjali a nasadzovali aktualizácie verzií. Poďme. Prezrite si nasledujúci kód PHP:

1

dva

3

4

5

6

7

8

9

10

jedenásť

12

13

14

pätnásť

16

17

18

$size = pow(2, 16)

$startTime = microtime(true)

$array = array()

for ($key = 0, $maxKey = ($size - 1) * $size$key <= $maxKey$key += $size) {

$array[$key] = 0

}

$endTime = microtime(true)

echo 'insert', $size, 'A malicious element needs', $endTime - $startTime, ' second', ' '

$startTime = microtime(true)

$array = array()

for ($key = 0, $maxKey = $size - 1 $key <= $maxKey ++$key) {

$array[$key] = 0

}

$endTime = microtime(true)

echo 'insert', $size, 'Ordinary elements need', $endTime - $startTime, ' second', ' '

Vyššie uvedené ukážka predstavuje porovnanie spotreby času, keď existuje viac konfliktov hash a žiadne konflikty. Tento kód spustím pod PHP5.4, výsledok je nasledovný

Vloženie 65 536 škodlivých prvkov vyžaduje 43,72204709053 sekúnd

Vloženie 65 536 normálnych prvkov vyžaduje 0,009843111038208 sekúnd

A výsledok fungovania na PHP7:

Vloženie 65 536 škodlivých prvkov vyžaduje 4,4028408527374 sekúnd

Vloženie 65 536 bežných prvkov vyžaduje 0,0018510818481445 sekúnd

Je vidieť, že výkon PHP7 sa výrazne zlepšil v konfliktných aj bezkonfliktných operáciách s poľami. Samozrejme, zjavnejšie sú protichodné zlepšenia výkonu. Pokiaľ ide o to, prečo sa výkon PHP7 tak zlepšil, stojí za to sa mu naďalej venovať.

Nakoniec má autor na stránkach github jednoduchú verziu implementácie HashTable: implementácia HashTable

Okrem toho mám podrobnejšiu anotáciu zdrojového kódu PHP na github. Záujemcovia sa môžu rozhliadnuť okolo seba, dať hviezdičku. Poznámky k zdroju PHP5.4. Pridané anotácie môžete zobraziť prostredníctvom záznamu o potvrdení.