백엔드 개발 PHP 튜토리얼 详解PHP中Array构造HashTable

详解PHP中Array构造HashTable

Jun 13, 2016 pm 01:08 PM
gt hash hashtable null

详解PHP中Array结构HashTable

我们知道PHP中的Array在内部是以Hash的结构进行存储的。本文主要重点也是对PHP中Array的静态结构和动态结构进行分析和记录。

这里的静态结构,是指存储PHP中Array数据时使用的数据结构,即所谓的HashTable。

动态结构,是指程序在运行过程中,Array数据的存储状态。

?

首先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中的Array在内部对应一个HashTable,HashTable内部的四个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)
로그인 후 복사
?

?

然后计算要插入的字符串索引的hash值,并与nTableMask做按位与,得到nindex,这个nIndex就是对应的bucket*在二维数组arBucket**中的偏移量。根据代码逻辑,如果nIndex位置不为空,则说明当前计算得到的hash值之前存在。如果连key也相同并且flag为HASH_ADD则失败,否则就是更新操作。如果是更新操作则不会对现有数组结构有任何影响,更新了对应的值之后直接退出即可。

?

在需要有新元素插入到HashTable时,构造好的新元素会经过两步来链入该HashTable

?

第一步代码如下:

?

#define CONNECT_TO_BUCKET_DLLIST(element, list_head)        \
    (element)->pNext = (list_head);                         \
    (element)->pLast = NULL;                                \
    if ((element)->pNext) {                                 \
        (element)->pNext->pLast = (element);                \
    }

로그인 후 복사

?

在这一步中如果新元素的key的hash值之前存在过,则list_head为HashTable.arBucket[nIndex],nIndex怎么来的前面已经说过了。在这一步过后会将HashTable.arBucket[nIndex]赋值为当前的新元素,你懂得。

?

如果新元素的key对应的hash之前没有存在过,则list_head就为NULL,因为HashTable.arBucket[nIndex]为NULL。你也懂得。

?

第二步代码如下:

?

#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.

插入第一个元素A,假设其key对应的hash值为1

则插入之后,内存中的状态如下:

?

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.

插入第二个元素B,假设其key对应的hash值为2

则插入之后内存的状态如下:

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.

插入第三个元素C,假设其key的hash值为1,和A相同

则插入之后内存状态如下:

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三个值之后的内存中状态即为:

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,A、B、C三个元素都已插入了,现在我们要实现两个任务:

?

1.

查找某key的元素值(value):

如果我们要访问A元素,则提供A的key:key_a,得到对应的hash值为1

然后找HastTable.arBucket[1]。这时HastTable.arBucket[1]其实为C不是A,但由于C的key不等于A的key,因此,要沿着pNext的指针找下去,直到NULL,而此时C.pNext就是A,即找到了key_a对应的值A。

总之由key查找一个元素时,首先要hash,然后顺着hash后的索引位置的pNext指针一直找下去,直到NULL,如果遇到了和要查找的key相同的值,则找到,否则找不到。

?

2.

遍历数组:

由于我们的例子中的key是字符串类型的,全部循环遍历不能用for。只能用foreach,那foreach的遍历是如何实现的呢?

?

简单,根据最后的HashTable的状态,我们从HastTable.pListHead开始沿着pListNext指针顺序找下去即可了。以本文例子为例,则结果为:

?

?

HashTable.pListHead====>A

A.pListNext?????????????????? ====>B

B.pListNext?????????????????? ====>C

?

则最后的遍历顺序就是A,B,C,发现foreach的遍历顺序是和元素插入到数组的顺序相关的。

?

?

如果插入的元素的key不是字符串,而是数值。则可以省去做计算hash值这一步,直接拿数值的key做为hash值使用。

这样就不存在hash冲突的问题,这样也就不会用到每个元素的pNext、pLast两个指针了,这两个指针都只会是NULL。

?

这样我们可以通过使用for循环来遍历数组了,因为不存在hash冲突。

?

同样,如果我们使用foreach来遍历数组的话,遍历顺序还是元素的插入顺序,这个你当然懂得。

?

?

ps:

本文并未对zend中的hash结够做全面的记录,只是对本文主题涉及到的逻辑的重点代码进行了分析和演示。同时也为了能抓住重点。有些代码并未列出,如:再hash的逻辑,和索引为数值类型数据的代码等。这些可在代码文件Zend/zend_hash.c中找到详细内容。

?

본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

인기 기사

R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 최고의 그래픽 설정
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25 : Myrise에서 모든 것을 잠금 해제하는 방법
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

화웨이 GT3 Pro와 GT4의 차이점은 무엇입니까? 화웨이 GT3 Pro와 GT4의 차이점은 무엇입니까? Dec 29, 2023 pm 02:27 PM

많은 사용자들이 스마트 시계를 선택할 때 Huawei 브랜드를 선택하게 됩니다. 그 중 Huawei GT3pro와 GT4가 가장 인기 있는 선택입니다. 두 제품의 차이점을 궁금해하는 사용자가 많습니다. Huawei GT3pro와 GT4의 차이점은 무엇입니까? 1. 외관 GT4: 46mm와 41mm, 재질은 유리 거울 + 스테인레스 스틸 본체 + 고해상도 섬유 후면 쉘입니다. GT3pro: 46.6mm 및 42.9mm, 재질은 사파이어 유리 + 티타늄 본체/세라믹 본체 + 세라믹 백 쉘입니다. 2. 건강한 GT4: 최신 Huawei Truseen5.5+ 알고리즘을 사용하면 결과가 더 정확해집니다. GT3pro: ECG 심전도, 혈관 및 안전성 추가

수정: Windows 11에서 캡처 도구가 작동하지 않음 수정: Windows 11에서 캡처 도구가 작동하지 않음 Aug 24, 2023 am 09:48 AM

Windows 11에서 캡처 도구가 작동하지 않는 이유 문제의 근본 원인을 이해하면 올바른 솔루션을 찾는 데 도움이 될 수 있습니다. 캡처 도구가 제대로 작동하지 않는 주요 이유는 다음과 같습니다. 초점 도우미가 켜져 있습니다. 이렇게 하면 캡처 도구가 열리지 않습니다. 손상된 응용 프로그램: 캡처 도구가 실행 시 충돌하는 경우 응용 프로그램이 손상되었을 수 있습니다. 오래된 그래픽 드라이버: 호환되지 않는 드라이버가 캡처 도구를 방해할 수 있습니다. 다른 응용 프로그램의 간섭: 실행 중인 다른 응용 프로그램이 캡처 도구와 충돌할 수 있습니다. 인증서가 만료되었습니다. 업그레이드 프로세스 중 오류로 인해 이 문제가 발생할 수 있습니다. 이 문제는 대부분의 사용자에게 적합하며 특별한 기술 지식이 필요하지 않습니다. 1. Windows 및 Microsoft Store 앱 업데이트

PHP에서 Redis Hash 작업을 구현하는 방법 PHP에서 Redis Hash 작업을 구현하는 방법 May 30, 2023 am 08:58 AM

해시 연산 //해시 테이블의 필드에 값을 할당합니다. 성공하면 1을, 실패하면 0을 반환합니다. 해시 테이블이 없으면 테이블이 먼저 생성된 후 값이 할당됩니다. 필드가 이미 있으면 이전 값을 덮어씁니다. $ret=$redis->hSet('user','realname','jetwu');//해시 테이블에서 지정된 필드의 값을 가져옵니다. 해시 테이블이 없으면 false를 반환합니다. $ret=$redis->hGet('사용자','지역

C 언어에서 null과 NULL의 차이점은 무엇입니까? C 언어에서 null과 NULL의 차이점은 무엇입니까? Sep 22, 2023 am 11:48 AM

C 언어에서 null과 NULL의 차이점은 다음과 같습니다. null은 C 언어의 매크로 정의로, 일반적으로 포인터 변수를 초기화하거나 조건문에서 포인터가 null인지 확인하는 데 사용할 수 있는 null 포인터를 나타내는 데 사용됩니다. NULL은 C 언어의 매크로 정의입니다. 일반적으로 널 포인터, 널 포인터 배열 또는 널 구조 포인터를 나타내는 데 사용되는 널 값을 나타내는 데 사용되는 미리 정의된 상수입니다.

iPhone에서 App Store 오류에 연결할 수 없는 문제를 해결하는 방법 iPhone에서 App Store 오류에 연결할 수 없는 문제를 해결하는 방법 Jul 29, 2023 am 08:22 AM

1부: 초기 문제 해결 단계 Apple 시스템 상태 확인: 복잡한 솔루션을 살펴보기 전에 기본 사항부터 시작해 보겠습니다. 문제는 귀하의 기기에 있는 것이 아닐 수도 있습니다. Apple 서버가 다운되었을 수도 있습니다. Apple의 시스템 상태 페이지를 방문하여 AppStore가 제대로 작동하는지 확인하세요. 문제가 있는 경우 Apple이 문제를 해결하기를 기다리는 것뿐입니다. 인터넷 연결 확인: "AppStore에 연결할 수 없음" 문제는 때때로 연결 불량으로 인해 발생할 수 있으므로 인터넷 연결이 안정적인지 확인하십시오. Wi-Fi와 모바일 데이터 간을 전환하거나 네트워크 설정을 재설정해 보세요(일반 > 재설정 > 네트워크 설정 재설정 > 설정). iOS 버전을 업데이트하세요.

정의되지 않음과 null은 무엇을 의미하나요? 정의되지 않음과 null은 무엇을 의미하나요? Nov 20, 2023 pm 02:39 PM

JavaScript에서 undefound와 null은 모두 "아무것도 없음"이라는 개념을 나타냅니다. 1. undefine은 초기화되지 않은 변수 또는 존재하지 않는 속성을 나타냅니다. 변수가 선언되었지만 값이 할당되지 않은 경우 변수의 값은 undefine입니다. 개체에 존재하지 않는 속성에 액세스하는 경우 반환된 값도 정의되지 않습니다. 2. null은 빈 개체 참조를 나타내는 경우가 있으며, 개체 참조가 차지하는 메모리를 해제하기 위해 null로 설정할 수 있습니다.

Java에서 Hashtable 클래스의 isEmpty() 메서드를 사용하여 해시 테이블이 비어 있는지 확인 Java에서 Hashtable 클래스의 isEmpty() 메서드를 사용하여 해시 테이블이 비어 있는지 확인 Jul 24, 2023 pm 02:21 PM

Java에서는 Hashtable 클래스의 isEmpty() 메서드를 사용하여 해시 테이블이 비어 있는지 확인합니다. 해시 테이블은 Java 컬렉션 프레임워크에서 일반적으로 사용되는 데이터 구조 중 하나이며 키-값의 저장 및 검색을 구현합니다. 한 쌍. Hashtable 클래스에서는 isEmpty() 메서드를 사용하여 해시 테이블이 비어 있는지 확인합니다. 이 기사에서는 Hashtable 클래스의 isEmpty() 메서드를 사용하는 방법을 소개하고 해당 코드 예제를 제공합니다. 먼저 Hashtable 클래스를 이해해야 합니다. 해시시

Laravel 개발: Laravel Hash를 사용하여 비밀번호 해시를 생성하는 방법은 무엇입니까? Laravel 개발: Laravel Hash를 사용하여 비밀번호 해시를 생성하는 방법은 무엇입니까? Jun 17, 2023 am 10:59 AM

Laravel은 현재 가장 인기 있는 PHP 웹 프레임워크 중 하나이며 개발자에게 많은 강력한 기능과 구성 요소를 제공하며 LaravelHash도 그 중 하나입니다. LaravelHash는 비밀번호를 안전하게 유지하고 애플리케이션의 사용자 데이터를 더욱 안전하게 만드는 데 사용할 수 있는 비밀번호 해싱용 PHP 라이브러리입니다. 이 글에서는 LaravelHash의 작동 방식과 이를 사용하여 비밀번호를 해시하고 확인하는 방법을 알아봅니다. 라라 학습을 위한 필수 지식

See all articles