PHPの配列構造HashTable
の詳しい説明
PHP の配列は内部的にハッシュ構造に格納されることがわかっています。この記事の主な焦点は、PHP における配列の静的構造と動的構造を分析して記録することです。
ここでの静的構造とは、PHP で配列データを格納するときに使用されるデータ構造、いわゆる HashTable を指します。
動的構造とは、プログラムの実行プロセス中の配列データの格納状態を指します。
?
まず、PHP における hashTable の構造は次のとおりです。
typedef struct bucket { ulong h; /* Used for numeric indexing */ uint nKeyLength; void *pData; void *pDataPtr; struct bucket *pListNext; struct bucket *pListLast; struct bucket *pNext; struct bucket *pLast; char *arKey; } Bucket; typedef struct _hashtable { uint nTableSize; uint nTableMask; uint nNumOfElements; ulong nNextFreeElement; Bucket *pInternalPointer; /* Used for element traversal */ 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;
?
PHP の配列は、内部的には HashTable に対応しており、HashTable 内の 4 つの Bucket 型ポインタ データは、実際に配列に格納されている要素コンテンツのアドレスを記録します。具体的な内容と各フィールド名は一目瞭然なので、これ以上の説明は不要です。
?
?
これらのコード行だけを見ても、PHP 配列の実際の動作原理を理解できないかもしれません。次に、PHP 配列の最も単純な操作のいくつかを手動でシミュレートします。
?
1. ゼロから
HashTable を初期化するには、まず HashTable のメモリ空間を構築する必要があります。具体的なコードは次のとおりです。
?
//hash_func_t在函数内用不到,hash函数在PHP范围内都是固定的 int _zend_hash_init(HashTable *ht, uint nSize, hash_func_t pHashFunction, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_DC) { uint i = 3; SET_INCONSISTENT(HT_OK); if (nSize >= 0x80000000) { /* prevent overflow */ ht->nTableSize = 0x80000000; } else { while ((1U nTableSize = 1 nTableMask = 0; /* 0 means that ht->arBuckets is uninitialized */ ht->pDestructor = pDestructor; ht->arBuckets = (Bucket**)&uninitialized_bucket; ? //实际的数据存储空间还未创建 ht->pListHead = NULL; ht->pListTail = NULL; ht->nNumOfElements = 0; ? ? ? ? ? ? ? ? ? //表示数组内还没有一个元素, ht->nNextFreeElement = 0; ht->pInternalPointer = NULL; ht->persistent = persistent; ht->nApplyCount = 0; ht->bApplyProtection = 1; return SUCCESS; }?
上記のコードは、データが対応するメモリ ブロックに入ることができる、配列の一般的なゲートを構築するものとして理解できます。もちろん、ドアにはまだ「座席」はありません。
?
2. データ挿入
何もない空間に何かを加えるにはどうすればよいでしょうか?これはデータの挿入、つまりデータがこの HashTable に保存される方法です。
PHP の配列インデックスは数値または文字列にすることができます。まず、文字列インデックスがどのように格納されるかを見てみましょう。
int _zend_hash_add_or_update(HashTable *ht, const char *arKey, uint nKeyLength, void *pData, uint nDataSize, void **pDest, int flag ZEND_FILE_LINE_DC) { ulong h; uint nIndex; Bucket *p; IS_CONSISTENT(ht); if (nKeyLength nTableMask; p = ht->arBuckets[nIndex]; while (p != NULL) { if (p->arKey == arKey || ((p->h == h) && (p->nKeyLength == nKeyLength) && !memcmp(p->arKey, arKey, nKeyLength))) { if (flag & HASH_ADD) { return FAILURE; } HANDLE_BLOCK_INTERRUPTIONS(); #if ZEND_DEBUG if (p->pData == pData) { ZEND_PUTS("Fatal error in zend_hash_update: p->pData == pData\n"); HANDLE_UNBLOCK_INTERRUPTIONS(); return FAILURE; } #endif if (ht->pDestructor) { ht->pDestructor(p->pData); } UPDATE_DATA(ht, p, pData, nDataSize); if (pDest) { *pDest = p->pData; } HANDLE_UNBLOCK_INTERRUPTIONS(); return SUCCESS; ?//更新之后直接退出 } p = p->pNext; } if (IS_INTERNED(arKey)) { p = (Bucket *) pemalloc(sizeof(Bucket), ht->persistent); if (!p) { return FAILURE; } p->arKey = (char*)arKey; } else { p = (Bucket *) pemalloc(sizeof(Bucket) + nKeyLength, ht->persistent); if (!p) { return FAILURE; } p->arKey = (char*)(p + 1); memcpy(p->arKey, arKey, nKeyLength); } p->nKeyLength = nKeyLength; INIT_DATA(ht, p, pData, nDataSize); p->h = h; CONNECT_TO_BUCKET_DLLIST(p, ht->arBuckets[nIndex]); if (pDest) { *pDest = p->pData; } HANDLE_BLOCK_INTERRUPTIONS(); CONNECT_TO_GLOBAL_DLLIST(p, ht); ht->arBuckets[nIndex] = p; HANDLE_UNBLOCK_INTERRUPTIONS(); ht->nNumOfElements++; ZEND_HASH_IF_FULL_DO_RESIZE(ht); /* If the Hash table is full, resize it */ return SUCCESS; }
まず、配列空間が初期化されているかどうかを確認します。コードは次のとおりです。
??
#define CHECK_INIT(ht) do { \ if (UNEXPECTED((ht)->nTableMask == 0)) { \ (ht)->arBuckets = (Bucket **) pecalloc((ht)->nTableSize, sizeof(Bucket *), (ht)->persistent); \ (ht)->nTableMask = (ht)->nTableSize - 1; \ } \ } while (0)?
次に、挿入する文字列インデックスのハッシュ値を計算し、nTableMask とビットごとの AND を実行して、nindex を取得します。この nIndex は、2 次元配列 arBucket** 内の対応するバケット* のオフセットです。コードロジックによれば、nIndex の位置が空でない場合は、現在計算されているハッシュ値が以前に存在することを意味します。キーが同じでフラグが HASH_ADD の場合は失敗し、それ以外の場合は更新操作になります。更新操作の場合、既存の配列構造には影響しません。対応する値を更新した後、直接終了できます。
?
新しい要素をハッシュテーブルに挿入する必要がある場合、構築された新しい要素は 2 つのステップでハッシュテーブルにリンクされます
?
最初のステップのコードは次のとおりです:
?
#define CONNECT_TO_BUCKET_DLLIST(element, list_head) \ (element)->pNext = (list_head); \ (element)->pLast = NULL; \ if ((element)->pNext) { \ (element)->pNext->pLast = (element); \ }?
このステップでは、新しい要素のキーのハッシュ値が以前に存在していた場合、list_head は HashTable.arBucket[nIndex] になります。nIndex の取得方法は前述しました。このステップの後、HashTable.arBucket[nIndex] には現在の新しい要素の値が割り当てられます。
?
新しい要素のキーに対応するハッシュが以前に存在しなかった場合、HashTable.arBucket[nIndex] が NULL であるため、list_head は NULL になります。あなたも理解しています。
?
2 番目のステップのコードは次のとおりです:
?
?
#define CONNECT_TO_GLOBAL_DLLIST(element, ht) \ (element)->pListLast = (ht)->pListTail; \ (ht)->pListTail = (element); \ (element)->pListNext = NULL; \ if ((element)->pListLast != NULL) { \ (element)->pListLast->pListNext = (element); \ } \ if (!(ht)->pListHead) { \ (ht)->pListHead = (element); \ } \ if ((ht)->pInternalPointer == NULL) { \ (ht)->pInternalPointer = (element); \ }このステップが HashTable のコンテンツにどのような影響を与えるかについては、以下の動的例を参照してください。あなたも理解していると思います。
?
?
?
動的例:
ここで、配列内に要素がないと仮定して、挿入操作を実行します。次に、コードのロジックに従い、データ挿入プロセスを手動でシミュレートします。
?
1.
そのキーに対応するハッシュ値が 1 であると仮定して、最初の要素 A を挿入します
挿入後のメモリ内のステータスは次のとおりです:
?
HashTable.arBucket[1]=A;
HashTable.pListHead = A
HashTable.pListTail = A
HashTable.pInternalPointer = A
A.pNext = null
A.pLast = null
A.pListLast = null
A.pListNext = null
?
2.
そのキーに対応するハッシュ値が 2 であると仮定して、2 番目の要素 B を挿入します
挿入後のメモリの状態は次のようになります。
HashTable.arBucket[2] = B;
HashTable.pListHead = A
HashTable.pListTail = B
HashTable.pInternalPointer = A?????? //これは初回のみ設定されます
A.pNext=null
A.pLast = null
A.pListNext = B
A.pListLast = null
B.pListLast = A
B.pListNext = null
B.pNext = null
B.pLast = null
?
3.
そのキーのハッシュ値が A と同じ 1 であると仮定して、3 番目の要素 C を挿入します
挿入後のメモリの状態は次のようになります。
HashTable.arBucket[1] = C;
HashTable.pListHead = A
HashTable.pListTail =C
HashTable.pInternalPointer = A?????? //これは初回のみ設定されます
A.pNext=null
A.pLast = C
A.pListNext = B
A.pListLast = null
?
B.pNext = null
B.pLast = null
B.pListLast = A
B.pListNext = C
C.pNext = A
C.pLast = null
C.pListNext = null
C.pListLast = B
?
A、B、C の 3 つの値を挿入した後のメモリ内の状態は次のとおりです。
HashTable.arBucket[1] = C;
HashTable.pListHead = A
HashTable.pListTail =C
HashTable.pInternalPointer = A
A.pNext=null
A.pLast = C
A.pListNext = B
A.pListLast = null
?
B.pNext = null
B.pLast = null
B.pListLast = A
B.pListNext = C
C.pNext = A
C.pLast = null
C.pListNext = null
C.pListLast = B
?
OK、3 つの要素 A、B、C が挿入されました。次に、2 つのタスクを実装する必要があります。
?
1.
キーの要素値 (値) を検索します:
A 要素にアクセスしたい場合は、A のキー key_a を指定し、対応するハッシュ値 1 を取得します
次に、HstTable.arBucket[1] を見つけます。このとき、HstTable.arBucket[1]は実際にはAではなくCですが、CのキーはAのキーと等しくないため、pNextポインタに沿ってNULLになるまで検索する必要があり、この時点ではCです。 pNext は A、つまり、key_a に対応する値 A を取得します。
つまり、キーで要素を検索する場合は、まず要素をハッシュし、ハッシュ後のインデックス位置で pNext ポインタに沿って、探しているキーと同じ値が見つかるまで検索を続ける必要があります。 、それを見つけます、それ以外の場合はそれ未満を見つけます。
?
2.
配列を走査します:
この例のキーは文字列型であるため、すべてのループ走査で for を使用できるわけではありません。 foreach しか使用できないのですが、foreach トラバーサルはどのように実装されるのでしょうか?
?
簡単に言うと、最後の HashTable のステータスに従って、HstTable.pListHead から開始して、pListNext ポインタに沿って順番に検索します。この記事の例を例にとると、結果は次のようになります:
?
?
HashTable.pListHead====>A
A.pListNext??????????????????====>B
B.pListNext??????????????????====>C
?
最終的な走査順序は A、B、C になります。 foreach の走査順序は、要素が配列に挿入される順序に関連していることがわかります。
?
?
挿入された要素のキーが文字列ではなく数値の場合。これにより、ハッシュ値を計算する手順を省略し、数値キーを直接ハッシュ値として使用できます。
この方法では、ハッシュの競合の問題は発生せず、各要素の pNext ポインターと pLast ポインターは両方とも NULL のみになります。
?
この方法では、ハッシュの競合がないため、for ループを使用して配列を走査できます。
?
同様に、foreach を使用して配列を走査する場合、走査順序は依然として要素の挿入順序であることは理解されています。
?
?
追記:
この記事は、zend のハッシュ構造を完全に記録しているわけではありません。この記事のトピックに関係するロジックの主要なコードを分析して説明するだけです。同時に要点を把握するため。再ハッシュ ロジックや数値データのインデックスを作成するコードなど、一部のコードはリストされていません。これらの詳細は、コード ファイル Zend/zend_hash.c にあります。
?

データベースストレージセッションを使用することの主な利点には、持続性、スケーラビリティ、セキュリティが含まれます。 1。永続性:サーバーが再起動しても、セッションデータは変更されないままになります。 2。スケーラビリティ:分散システムに適用され、セッションデータが複数のサーバー間で同期されるようにします。 3。セキュリティ:データベースは、機密情報を保護するための暗号化されたストレージを提供します。

PHPでのカスタムセッション処理の実装は、SessionHandlerInterfaceインターフェイスを実装することで実行できます。具体的な手順には、次のものが含まれます。1)CussentsessionHandlerなどのSessionHandlerInterfaceを実装するクラスの作成。 2)セッションデータのライフサイクルとストレージ方法を定義するためのインターフェイス(オープン、クローズ、読み取り、書き込み、破壊、GCなど)の書き換え方法。 3)PHPスクリプトでカスタムセッションプロセッサを登録し、セッションを開始します。これにより、データをMySQLやRedisなどのメディアに保存して、パフォーマンス、セキュリティ、スケーラビリティを改善できます。

SessionIDは、ユーザーセッションのステータスを追跡するためにWebアプリケーションで使用されるメカニズムです。 1.ユーザーとサーバー間の複数のインタラクション中にユーザーのID情報を維持するために使用されるランダムに生成された文字列です。 2。サーバーは、ユーザーの複数のリクエストでこれらの要求を識別および関連付けるのに役立つCookieまたはURLパラメーターを介してクライアントに生成および送信します。 3.生成は通常、ランダムアルゴリズムを使用して、一意性と予測不可能性を確保します。 4.実際の開発では、Redisなどのメモリ内データベースを使用してセッションデータを保存してパフォーマンスとセキュリティを改善できます。

APIなどのステートレス環境でのセッションの管理は、JWTまたはCookieを使用して達成できます。 1。JWTは、無国籍とスケーラビリティに適していますが、ビッグデータに関してはサイズが大きいです。 2.cookiesはより伝統的で実装が簡単ですが、セキュリティを確保するために慎重に構成する必要があります。

セッション関連のXSS攻撃からアプリケーションを保護するには、次の測定が必要です。1。セッションCookieを保護するためにHTTPonlyとセキュアフラグを設定します。 2。すべてのユーザー入力のエクスポートコード。 3.コンテンツセキュリティポリシー(CSP)を実装して、スクリプトソースを制限します。これらのポリシーを通じて、セッション関連のXSS攻撃を効果的に保護し、ユーザーデータを確保できます。

PHPセッションのパフォーマンスを最適化する方法は次のとおりです。1。遅延セッション開始、2。データベースを使用してセッションを保存します。これらの戦略は、高い並行性環境でのアプリケーションの効率を大幅に改善できます。

thesession.gc_maxlifettinginttinginphpdethinesthelifsessessiondata、setinseconds.1)it'sconfiguredinphp.iniorviaini_set()。 2)AbalanceSneededToAvoidPerformanceIssues andunexpectedLogouts.3)php'sgarbagecollectionisisprobabilistic、影響を受けたBygc_probabi

PHPでは、session_name()関数を使用してセッション名を構成できます。特定の手順は次のとおりです。1。session_name()関数を使用して、session_name( "my_session")などのセッション名を設定します。 2。セッション名を設定した後、session_start()を呼び出してセッションを開始します。セッション名の構成は、複数のアプリケーション間のセッションデータの競合を回避し、セキュリティを強化することができますが、セッション名の一意性、セキュリティ、長さ、設定タイミングに注意してください。


ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

Safe Exam Browser
Safe Exam Browser は、オンライン試験を安全に受験するための安全なブラウザ環境です。このソフトウェアは、あらゆるコンピュータを安全なワークステーションに変えます。あらゆるユーティリティへのアクセスを制御し、学生が無許可のリソースを使用するのを防ぎます。

AtomエディタMac版ダウンロード
最も人気のあるオープンソースエディター

SAP NetWeaver Server Adapter for Eclipse
Eclipse を SAP NetWeaver アプリケーション サーバーと統合します。

SublimeText3 中国語版
中国語版、とても使いやすい

SecLists
SecLists は、セキュリティ テスターの究極の相棒です。これは、セキュリティ評価中に頻繁に使用されるさまざまな種類のリストを 1 か所にまとめたものです。 SecLists は、セキュリティ テスターが必要とする可能性のあるすべてのリストを便利に提供することで、セキュリティ テストをより効率的かつ生産的にするのに役立ちます。リストの種類には、ユーザー名、パスワード、URL、ファジング ペイロード、機密データ パターン、Web シェルなどが含まれます。テスターはこのリポジトリを新しいテスト マシンにプルするだけで、必要なあらゆる種類のリストにアクセスできるようになります。

ホットトピック









