むしろ波が行う妊娠中の置き換えマシンメカニズム試験管を持っていますか?妊娠複数少ないとパッケージコスト緑を代入

マイクロ・シグナルは█████サロゲートサロゲート少年生まれパケット███████IVF代理少年から選ばセックス代理█誕生セックスの管パケットの3つの世代から選ば7771███ドナーエッグIVFのIVF★2605★█138█します██ドナーエッグIVFの代理出産██

非常に重要なデータ構造であるPHPのコアでは、ハッシュテーブルです。私たちは、達成するためにハッシュテーブルを使用することですカーネルの配列を使用していました。だから、PHPはハッシュテーブルは、それを達成する方法はありますか?最近のデータ構造ハッシュテーブルが、アルゴリズムの本を読んで具体的なアルゴリズムが存在しない、ちょうど最近、PHPのソースコードなので、PHPハッシュテーブル、ハッシュテーブルの簡易版の内部実装のリファレンス実装を読んでも、いくつかの経験をまとめたもので、次のあなたと共有します。

それはgithubの上のハッシュテーブルの簡易版を実装しました:ハッシュテーブル達成します

また、私はgithubのより詳細なコメントにPHPのソースコードを持っています。興味見物人は星を与え、見ることができます。PHP5.4ソースのメモすることができ、レコードのコミットレビューコメントを追加しました。

ハッシュテーブルの紹介

辞書の効率的な運用を実現するために、ハッシュテーブルのデータ構造です。

定義

簡単に言えば、ハッシュテーブル(ハッシュテーブル)は、キーと値のペアのデータ構造です。挿入、検索、削除などの操作をサポートしています。いくつかの合理的な仮定では、ハッシュテーブル内のすべての操作の時間計算量は、(1)(関連関心が自身の検査を証明することができる)Oです。

ハッシュテーブルへの鍵

ハッシュテーブルでは、キーワードインデックスを使用しませんが、添字としてハッシュ関数によってキーのハッシュ値を計算し、[検索/キーのハッシュ値が削除されて再計算し、それによって、位置決め要素は、高速成り立ちます場所。

ハッシュテーブルに、異なるキーワードは、「ハッシュの衝突」と呼ばれる状況の2つ以上のキーの同じハッシュ値に対応するようになっている同一のハッシュ値を計算することができます。解決策はように多くのハッシュ衝突、オープン対処する方法、ジッパー方法とがあります。

したがって、キーは、良好なハッシュテーブルの実装のハッシュ衝突を処理するための良好なハッシュ関数及び方法です。

ハッシュ関数

ハッシュアルゴリズムの品質を決定することは、4つの定義を有する:> *一貫性は、必ずしも等しい等価キーのハッシュ値を生成する;> *効率的な、簡単な計算;> *均質、均一にすべてのキーハッシュ。

ハッシュ関数及びキー値のハッシュ値、すなわち:H = hash_func(キー)との対応関係を確立します。対応関係は以下の通り:

ハッシュ試験

それを行うには専門家に引き渡さ上で完璧なハッシュ関数を設計し、私たちはうまく既存の成熟したハッシュ関数を使用します。PHPハッシュ関数は、以下のように実装されてもDJBX33Aとして知られているカーネル関数time33、によって使用されます。

静的インラインULONG zend_inline_ hash_func(CONSTチャー* arKey、UINT nKeyLength)
{ 
         ULONGレジスタ= 5381ハッシュ。

        / *を有する変異体ハッシュが展開8 倍* / 用(; nKeyLength> = 8; nKeyLength - = 8){ ハッシュ=((ハッシュ<< 5)+ ハッシュ)+ * arKey ++。ハッシュ=((ハッシュ<< 5)+ ハッシュ)+ * arKey ++; ハッシュ=((ハッシュ<< 5)+ ハッシュ)+ * arKey ++; ハッシュ=((ハッシュ<< 5)+ ハッシュ)+ * arKey ++; ハッシュ=((ハッシュ<< 5)+ ハッシュ)+ * arKey ++; ハッシュ=((ハッシュ<< 5)+ ハッシュ)+ * arKey ++; ハッシュ=((ハッシュ<< 5)+ ハッシュ)+ * arKey ++; ハッシュ=((ハッシュ<< 5)+ ハッシュ)+ * arKey ++; }スイッチ(nKeyLength){ ケース7:ハッシュ=((ハッシュ<< 5)+ ハッシュ)+ * arKey ++。/ *フォールスルー... * / ケース6:ハッシュ=((ハッシュ<< 5)+ ハッシュ)+ * arKey ++。/ *フォールスルー... * / ケース5:ハッシュ=((ハッシュ<< 5)+ ハッシュ)+ * arKey ++。/ *フォールスルー... * / ケース4:ハッシュ=((ハッシュ<< 5)+ ハッシュ)+ * arKey ++。/ *フォールスルー... * / ケース3:ハッシュ=((ハッシュ<< 5)+ ハッシュ)+ * arKey ++。/ *フォールスルー... * / ケース2:ハッシュ=((ハッシュ<< 5)+)+ * arKeyを++ハッシュ。/ *フォールスルー... * / ケース1:ハッシュ=((ハッシュ<< 5)+ ハッシュ)+ * arKey ++。ブレーク; ケース0:ブレーク。EMPTY_SWITCH_DEFAULT_CASE()} 戻りハッシュ。}

注:関数は+を達成するために、スイッチ8サイクルを使用して、サイクルのために最適化され、動作サイクルの数を減らし、その後内部スイッチの要素の残りの部分に横断する必要はありません。

ジッパーの法則

すべての要素が同じハッシュ値を持つ場所はジッパー方式と呼ばれるリンクリストに格納されています。キーに対応するハッシュ値を計算することによって初めて見つけ、その後、ハッシュリストの対応する値を見つけ、最終的なリストは順序に沿って適切な値を見つけます。保存後の図の具体的な構成は以下の通りです。

ハッシュテーブル-試験

PHPのハッシュテーブルの構造

ハッシュテーブルのデータ構造を簡単に紹介した後、PHPはハッシュテーブルを実現する方法を見続けます。

(写真は、ネットワークから、すなわち侵害は削除しました)

PHPのコアハッシュテーブル定義:

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,HashTable的大小,以2的倍数增长
  • nTableMask,用在与哈希值做与运算获得该哈希值的索引取值,arBuckets初始化后永远是nTableSize-1
  • nNumOfElements,HashTable当前拥有的元素个数,count函数直接返回这个值
  • nNextFreeElement,表示数字键值数组中下一个数字索引的位置
  • pInternalPointer,内部指针,指向当前成员,用于遍历元素
  • pListHead,指向HashTable的第一个元素,也是数组的第一个元素
  • pListTail,指向HashTable的最后一个元素,也是数组的最后一个元素。与上面的指针结合,在遍历数组时非常方便,比如reset和endAPI
  • arBuckets,包含bucket组成的双向链表的数组,索引用key的哈希值和nTableMask做与运算生成
  • pDestructor,删除哈希表中的元素使用的析构函数
  • persistent,标识内存分配函数,如果是TRUE,则使用操作系统本身的内存分配函数,否则使用PHP的内存分配函数
  • nApplyCount,保存当前bucket被递归访问的次数,防止多次递归
  • bApplyProtection,标识哈希表是否要使用递归保护,默认是1,要使用

举一个哈希与mask结合的例子:

例如,”foo”真正的哈希值(使用DJBX33A哈希函数)是193491849。如果我们现在有64容量的哈希表,我们明显不能使用它作为数组的下标。取而代之的是通过应用哈希表的mask,然后只取哈希表的低位。

hash           |        193491849  |     0b1011100010000111001110001001
& mask         | &             63  | &   0b0000000000000000000000111111
----------------------------------------------------------------------
= index        | = 9               | =   0b0000000000000000000000001001

因此,在哈希表中,foo是保存在arBuckets中下标为9的bucket向量中。

bucket结构体的定义

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,哈希值(或数字键值的key
  • nKeyLength,key的长度
  • pData,指向数据的指针
  • pDataPtr,指针数据
  • pListNext,指向HashTable中的arBuckets链表中的下一个元素
  • pListLast,指向HashTable中的arBuckets链表中的上一个元素
  • pNext,指向具有相同hash值的bucket链表中的下一个元素
  • pLast,指向具有相同hash值的bucket链表中的上一个元素
  • arKey,key的名称

PHP中的HashTable是采用了向量加双向链表的实现方式,向量在arBuckets变量保存,向量包含多个bucket的指针,每个指针指向由多个bucket组成的双向链表,新元素的加入使用前插法,即新元素总是在bucket的第一个位置。由上面可以看到,PHP的哈希表实现相当复杂。这是它使用超灵活的数组类型要付出的代价。

一个PHP中的HashTable的示例图如下所示:

PHP-ハッシュ・テーブル試験

HashTable相关API

  • zend_hash_init
  • zend_hash_add_or_update
  • zend_hash_find
  • zend_hash_del_key_or_index

zend_hash_init

函数执行步骤

  • 设置哈希表大小
  • 设置结构体其他成员变量的初始值 (包括释放内存用的析构函数pDescructor)

详细代码注解点击:zend_hash_init源码

注:

1、pHashFunction在此处并没有用到,php的哈希函数使用的是内部的zend_inline_hash_func

2、zend_hash_init执行之后并没有真正地为arBuckets分配内存和计算出nTableMask的大小,真正分配内存和计算nTableMask是在插入元素时进行CHECK_INIT检查初始化时进行。

zend_hash_add_or_update

函数执行步骤

  • 检查键的长度
  • 检查初始化
  • 计算哈希值和下标
  • 遍历哈希值所在的bucket,如果找到相同的key且值需要更新,则更新数据,否则继续指向bucket的下一个元素,直到指向bucket的最后一个位置
  • 为新加入的元素分配bucket,设置新的bucket的属性值,然后添加到哈希表中
  • 如果哈希表空间满了,则重新调整哈希表的大小

函数执行流程图

zend_hash_add_or_update

CONNECT_TO_BUCKET_DLLIST是将新元素添加到具有相同hash值的bucket链表。

CONNECT_TO_GLOBAL_DLLIST是将新元素添加到HashTable的双向链表。

详细代码和注解请点击:zend_hash_add_or_update代码注解

zend_hash_find

函数执行步骤

  • 计算哈希值和下标
  • 遍历哈希值所在的bucket,如果找到key所在的bucket,则返回值,否则,指向下一个bucket,直到指向bucket链表中的最后一个位置

详细代码和注解请点击:zend_hash_find代码注解

zend_hash_del_key_or_index

函数执行步骤

  • 计算key的哈希值和下标
  • 遍历哈希值所在的bucket,如果找到key所在的bucket,则进行第三步,否则,指向下一个bucket,直到指向bucket链表中的最后一个位置
  • 如果要删除的是第一个元素,直接将arBucket[nIndex]指向第二个元素;其余的操作是将当前指针的last的next执行当前的next
  • 调整相关指针
  • 释放数据内存和bucket结构体内存

详细代码和注解请点击:zend_hash_del_key_or_index代码注解

性能分析

PHP的哈希表的优点:PHP的HashTable为数组的操作提供了很大的方便,无论是数组的创建和新增元素或删除元素等操作,哈希表都提供了很好的性能,但其不足在数据量大的时候比较明显,从时间复杂度和空间复杂度看看其不足。

不足如下:

  • 保存数据的结构体zval需要单独分配内存,需要管理这个额外的内存,每个zval占用了16bytes的内存;
  • 在新增bucket时,bucket也是额外分配,也需要16bytes的内存;
  • 为了能进行顺序遍历,使用双向链表连接整个HashTable,多出了很多的指针,每个指针也要16bytes的内存;
  • 在遍历时,如果元素位于bucket链表的尾部,也需要遍历完整个bucket链表才能找到所要查找的值

PHP的HashTable的不足主要是其双向链表多出的指针及zval和bucket需要额外分配内存,因此导致占用了很多内存空间及查找时多出了不少时间的消耗。

后续

上面提到的不足,在PHP7中都很好地解决了,PHP7对内核中的数据结构做了一个大改造,使得PHP的效率高了很多,因此,推荐PHP开发者都将开发和部署版本更新吧。看看下面这段PHP代码:

<?php
$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 '插入 ', $size, ' 个恶意的元素需要 ', $endTime - $startTime, ' 秒', "\n"; $startTime = microtime(true); $array = array(); for ($key = 0, $maxKey = $size - 1; $key <= $maxKey; ++$key) { $array[$key] = 0; } $endTime = microtime(true); echo '插入 ', $size, ' 个普通元素需要 ', $endTime - $startTime, '秒'、"\ n"は、

上記コンフリクトフリーと比較すると、時間がかかるが、このデモでは、より多くのハッシュ競合です。私は以下の結果、PHP5.4でこれを実行します

65536個の挿入悪質な要素が43.72204709053秒を必要とします

65536個の共通の要素を挿入0.009843111038208秒を必要とします

PHP7結果に実行中:

65536の挿入悪質な要素は4.4028408527374秒を必要とします

65536個の共通の要素を挿入0.0018510818481445秒を必要とします

パフォーマンスPHP7、配列操作と競合フリーで競合があるかどうかを見ることはもちろん、パフォーマンスが矛盾していることがより明白で、多くのことを改善しています。そんなにパフォーマンスの向上はなぜPHP7については下に到達するために続けるに値します。

参考記事:

衝突ハッシュPHP配列の例

PHPの内部配列の実装( - パート4 PHP開発者のためのPHPのソースコード)の概要

PHPの新しいハッシュテーブルの実装

おすすめ

転載: www.cnblogs.com/SGYE518/p/10934612.html