• php5数组与php7数组区别


    http://ju.outofmemory.cn/entry/197064

    http://www.fzb.me/2015-9-16-php7-implementation-hashtable.html

    http://ju.outofmemory.cn/entry/154095

    http://www.laruence.com/2009/08/23/1065.html

    https://github.com/laruence/php7-internal/blob/master/zval.md

     

    https://github.com/laruence/php7-internal/blob/master/zval.md

    https://github.com/laruence/php7-internal/blob/master/zval.md

     

    https://github.com/laruence/php7-internal/blob/master/zval.md

     

    http://coolshell.cn/articles/11377.html

     

    http://0x1.im/blog/php/Internal-value-representation-in-PHP-7-part-1.html

     

    http://www.supmen.com/vnzdw8op3r.html

    https://segmentfault.com/a/1190000004124429

     

    http://www.csdn.net/article/2015-09-16/2825720

    http://blog.jobbole.com/96689/

    http://www.cnblogs.com/yanlingyin/archive/2011/12/07/2278961.html

    http://blog.csdn.net/itianyi/article/details/8593391

     

     

    http://www.laruence.com/2008/08/19/338.html

     

    http://bestphper.com/archives/75

    http://www.jianshu.com/p/9f1ae9840847

     

     https://github.com/laruence/php7-internal/pull/28/files?diff=split#diff-0e5d955754f7e87c931aa5e4f669881eL44

    php5数组的相关结构体

    typedef struct _hashtable {  
        uint nTableSize;//4 哈希表中Bucket的槽的数量,初始值为8,每次resize时以2倍速度增长
        uint nTableMask;//4   nTableSize-1 , 索引取值的优化
        uint nNumOfElements;//4  哈希表中Bucket中当前存在的元素个数,count()函数会直接返回此值
        ulong nNextFreeElement;//4  下一个数字索引的位置
        Bucket *pInternalPointer;   /* Used for element traversal 4*/  当前遍历的指针(foreach比for快的原因之一) 用于元素遍历
        Bucket *pListHead;//4  存储数组头元素指针
        Bucket *pListTail;//4  存储数组尾元素指针
        Bucket **arBuckets;//4  //指针数组,数组中每个元素都是指针 存储hash数组
        dtor_func_t pDestructor;//4   在删除元素时执行的回调函数,用于资源的释放 /* persistent 指出了Bucket内存分配的方式。如果persisient为TRUE,则使用操作系统本身的内存分配函数为Bucket分配内存,否则使用PHP的内存分配函数。*/
        zend_bool persistent;//1  
        unsigned char nApplyCount;//1  标记当前hash Bucket被递归访问的次数(防止多次递归)
        zend_bool bApplyProtection;//1  标记当前hash桶允许不允许多次访问,不允许时,最多只能递归3次
    #if ZEND_DEBUG  
        int inconsistent;//4  
    #endif  
    } HashTable; 
    
    typedef struct bucket {  
        ulong h;    /* Used for numeric indexing                4字节 */  对char *key进行hash后的值,或者是用户指定的数字索引值/* Used for numeric indexing */
        uint nKeyLength;    /* The length of the key (for string keys)  4字节 字符串索引长度,如果是数字索引,则值为0 */  
        void *pData;        /* 4字节 实际数据的存储地址,指向value,一般是用户数据的副本,如果是指针数据,则指向pDataPtr*/  //这里又是个指针,zval存放在别的地方
        void *pDataPtr;         /* 4字节 引用数据的存储地址,如果是指针数据,此值会指向真正的value,同时上面pData会指向此值 */  
        struct bucket *pListNext;  /* PHP arrays are ordered. This gives the next element in that order4字节 整个哈希表的该元素的下一个元素*/  
        struct bucket *pListLast;  /* and this gives the previous element           4字节 整个哈希表的该元素的上一个元素*/  
        struct bucket *pNext;      /* The next element in this (doubly) linked list     4字节 同一个槽,双向链表的下一个元素的地址 */  
        struct bucket *pLast;      /* The previous element in this (doubly) linked list     4字节 同一个槽,双向链表的上一个元素的地址*/  
        char arKey[1];            /* Must be last element   1字节 保存当前值所对于的key字符串,这个字段只能定义在最后,实现变长结构体*/  
    } Bucket;  

     

    数组的初始化

    ZEND_API 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;
        //...
        if (nSize >= 0x80000000) {
            /* prevent overflow */
            ht->nTableSize = 0x80000000;
        } else {
            while ((1U << i) < nSize) {
                i++;
            }
            ht->nTableSize = 1 << i;
        }
        // ...
        ht->nTableMask = ht->nTableSize - 1;
     
        /* Uses ecalloc() so that Bucket* == NULL */
        if (persistent) {
            tmp = (Bucket **) calloc(ht->nTableSize, sizeof(Bucket *));//sizeof(Bucket *)大小为4,也就是分配ht->nTableSise个指针
            if (!tmp) {
                return FAILURE;
            }
            ht->arBuckets = tmp;
        } else {
            tmp = (Bucket **) ecalloc_rel(ht->nTableSize, sizeof(Bucket *));
            if (tmp) {
                ht->arBuckets = tmp;
            }
        }
     
        return SUCCESS;
    }

     

     

    数组的插入、更新

    ZEND_API 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)
    {
         //...省略变量初始化和nKeyLength <=0 的异常处理
     
        h = zend_inline_hash_func(arKey, nKeyLength);
        nIndex = h & ht->nTableMask;
     
        p = ht->arBuckets[nIndex];
        while (p != NULL) {
            if ((p->h == h) && (p->nKeyLength == nKeyLength)) {
                if (!memcmp(p->arKey, arKey, nKeyLength)) { //  更新操作
                    if (flag & HASH_ADD) {
                        return FAILURE;
                    }
                    HANDLE_BLOCK_INTERRUPTIONS();
     
                    //..省略debug输出
                    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;
        }
     
        p = (Bucket *) pemalloc(sizeof(Bucket) - 1 + nKeyLength, ht->persistent);  //为Bucket分配内存,这时候的内存是不连续的,在print数组时,是在链表中挨个打印,内存地址是随机的,不能使用到内存的局部性
        if (!p) {
            return FAILURE;
        }
        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]); //Bucket双向链表操作
        if (pDest) {
            *pDest = p->pData;
        }
     
        HANDLE_BLOCK_INTERRUPTIONS();
        CONNECT_TO_GLOBAL_DLLIST(p, ht);    // 将新的Bucket元素添加到数组的链接表的最后面
        ht->arBuckets[nIndex] = p;
        HANDLE_UNBLOCK_INTERRUPTIONS();
     
        ht->nNumOfElements++;
        ZEND_HASH_IF_FULL_DO_RESIZE(ht);        /*  如果此时数组的容量满了,则对其进行扩容。*/
        return SUCCESS;
    }

     

    php5 bucket中的zval只是一個指針,因此還要多分配一個指針,而php7是直接在bucket中存儲zval

     

     

    ht->nTableMask的大小为ht->nTableSize -1。 这里使用&操作而不是使用取模,这是因为是相对来说取模操作的消耗和按位与的操作大很多。

    nTableMask的作用就是将哈希值映射到槽位所能存储的索引范围内。 例如:某个key的索引值是21, 哈希表的大小为8,则mask为7,则求与时的二进制表示为: 10101 & 111 = 101 也就是十进制的5。 因为2的整数次方-1的二进制比较特殊:后面N位的值都是1,这样比较容易能将值进行映射, 如果是普通数字进行了二进制与之后会影响哈希值的结果。那么哈希函数计算的值的平均分布就可能出现影响。

    由于php7中的bucket直接存储zval

    例如

    mystr = estrdup("Forty Five");
    add_next_index_string(return_value, mystr);

    ZEND_API int add_next_index_string(zval *arg, const char *str) /* {{{ */
    {
        zval tmp;
    
        ZVAL_STRING(&tmp, str);
        return zend_hash_next_index_insert(Z_ARRVAL_P(arg), &tmp) ? SUCCESS :     FAILURE;
    }
    
    #define ZVAL_STRINGL(z, s, l) do {                
        ZVAL_NEW_STR(z, zend_string_init(s, l, 0));        
    } while (0)
    
    //zend_string_init 本身是从堆中分配的内存
    static zend_always_inline zend_string *zend_string_init(const char *str, size_t len, int persistent)
    {
        zend_string *ret = zend_string_alloc(len, persistent);
    
        memcpy(ZSTR_VAL(ret), str, len);
        ZSTR_VAL(ret)[len] = '';
        return ret;
    }
    
    static zend_always_inline zend_string *zend_string_alloc(size_t len, int persistent)
    {
        zend_string *ret = (zend_string *)pemalloc(ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(len)), persistent);
    
        GC_REFCOUNT(ret) = 1;
    
        /* optimized single assignment */
        GC_TYPE_INFO(ret) = IS_STRING | ((persistent ? IS_STR_PERSISTENT : 0) << 8);
    
        zend_string_forget_hash_val(ret);
        ZSTR_LEN(ret) = len;
        return ret;
    }
    
    #define ZVAL_NEW_STR(z, s) do {                    
            zval *__z = (z);                        
            zend_string *__s = (s);                    
            Z_STR_P(__z) = __s;                        
            Z_TYPE_INFO_P(__z) = IS_STRING_EX;        
        } while (0)
    
    
    struct _zend_string {
        zend_refcounted_h gc;
        zend_ulong        h;                /* hash value */
        size_t            len;
        char              val[1];
    };

    php7数组结构体

    typedef struct _HashTable { 
        union {
            struct {
                ZEND_ENDIAN_LOHI_3(
                    zend_uchar    flags,
                    zend_uchar    nApplyCount,  /* 循环遍历保护 */
                    uint16_t      reserve)
            } v;
            uint32_t flags;
        } u;
        uint32_t          nTableSize;           /* hash表的大小 HashTable的大小,始终为2的指数(8,16,32,64...)。最小为8,最大值根据机器不同而不同*/
        uint32_t          nTableMask;           /* 掩码,用于根据hash值计算存储位置,永远等于nTableSize-1 */
        uint32_t          nNumUsed;             /* arData数组已经使用的数量 */
        uint32_t          nNumOfElements;       /* hash表中元素个数 */
        uint32_t          nInternalPointer;     /* 用于HashTable遍历 */
        zend_long         nNextFreeElement; /* 下一个空闲可用位置的数字索引 */
        Bucket           *arData;               /* 存放实际数据 */
        uint32_t         *arHash;               /* Hash表 */
        dtor_func_t       pDestructor;          /* 析构函数 */
    } HashTable;
    
    typedef struct _Bucket {
        zval              val;  
        zend_ulong        h;                /* hash value (or numeric index)   */
        zend_string      *key;              /* string key or NULL for numerics */
    } Bucket;

     

     存储中,最关键的两个是两个指针*arData和*arHash。其中,arData是Bucket的实际存储位置,在HashTable初始化的时候,会分配一块连续的能连续存放nTableSize个Bucket的内存,因此在使用时可以将其当作数组访问:arData[0], arData1……;arHash是一个nTableSize大小的数组,元素的key在hash之后落在0~(nTableSize-1)之间,这个数组是arData的索引,用于根据hash值迅速找到其对应的元素。

     

    数组初始化

    ZEND_API void ZEND_FASTCALL _zend_hash_init(HashTable *ht, uint32_t nSize, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_DC)
    {
        GC_REFCOUNT(ht) = 1;
        GC_TYPE_INFO(ht) = IS_ARRAY;
        ht->u.flags = (persistent ? HASH_FLAG_PERSISTENT : 0) | HASH_FLAG_APPLY_PROTECTION | HASH_FLAG_STATIC_KEYS;
        ht->nTableSize = zend_hash_check_size(nSize);
        ht->nTableMask = HT_MIN_MASK;
        HT_SET_DATA_ADDR(ht, &uninitialized_bucket); //在這裏已經為ht->arData分配內存,而有些文章上說在這個函數裏,ht->arData不會被初始化
        ht->nNumUsed = 0;
        ht->nNumOfElements = 0;
        ht->nInternalPointer = HT_INVALID_IDX;
        ht->nNextFreeElement = 0;
        ht->pDestructor = pDestructor;
    }
    #define HT_SET_DATA_ADDR(ht, ptr) do { 
            (ht)->arData = (Bucket*)(((char*)(ptr)) + HT_HASH_SIZE((ht)->nTableMask));  最少分配8个bucket
        } while (0)

     

    插入,更新

    static zend_always_inline zval *_zend_hash_add_or_update_i(HashTable *ht, zend_string *key, zval *pData, uint32_t flag ZEND_FILE_LINE_DC)
    {
        zend_ulong h;
        uint32_t nIndex;
        uint32_t idx;
        Bucket *p;
    
        IS_CONSISTENT(ht);
    
        if (UNEXPECTED(!(ht->u.flags & HASH_FLAG_INITIALIZED))) {   /* 检查hashtable是否初始化 */
            CHECK_INIT(ht, 0);
            goto add_to_hash; 
        } else if (ht->u.flags & HASH_FLAG_PACKED) {    /* ? */
            zend_hash_packed_to_hash(ht);
        } else if ((flag & HASH_ADD_NEW) == 0) {    /* 新增 */
            p = zend_hash_find_bucket(ht, key); /* 根据key查是否已经存在 */
    
            if (p) {    /* 当前的key已经存在 */
                zval *data;
    
                if (flag & HASH_ADD) {  /* key已经存在产生添加冲突,退出 */
                    return NULL;
                }
                ZEND_ASSERT(&p->val != pData);  /* key存在的情况下,值不一样做更新操作 */
                data = &p->val;
                if ((flag & HASH_UPDATE_INDIRECT) && Z_TYPE_P(data) == IS_INDIRECT) {
                    data = Z_INDIRECT_P(data);
                }
                HANDLE_BLOCK_INTERRUPTIONS();
                if (ht->pDestructor) {
                    ht->pDestructor(data);  /* 释放掉原来的data */
                }
                ZVAL_COPY_VALUE(data, pData);   /* 将新的pData值复制给原来的data */
                HANDLE_UNBLOCK_INTERRUPTIONS();
                return data;
            }
        }
    
        ZEND_HASH_IF_FULL_DO_RESIZE(ht);        /* If the Hash table is full, resize it */
    
    add_to_hash:
        HANDLE_BLOCK_INTERRUPTIONS();
        idx = ht->nNumUsed++;   /* 已使用计数+1,并且用老的位置来做为索引 */
        ht->nNumOfElements++;   /* 元素个数加1 */
        if (ht->nInternalPointer == INVALID_IDX) {
            ht->nInternalPointer = idx;
        }
        p = ht->arData + idx;   /* 指针加法移位 */
        p->h = h = zend_string_hash_val(key);   /* 计算key的hash值 */
        p->key = key;
        zend_string_addref(key);
        ZVAL_COPY_VALUE(&p->val, pData);
        nIndex = h & ht->nTableMask;    /* 与tablemask进行计算得出hash索引 */
        Z_NEXT(p->val) = ht->arHash[nIndex];    /* 新的元素的hash冲突链表的next指向当前冲突链表的首部元素 */
        ht->arHash[nIndex] = idx;       /* 新的元素放到当前hash冲突链表的头部 */
        HANDLE_UNBLOCK_INTERRUPTIONS();
    
        return &p->val;
    }
    
    #define Z_NEXT(zval)        (zval).u2.next

    php计算hash

    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;
    }

    相对于apache等其他软件使用的time33算法而言,PHP并没有直接乘33,而是使用的hash << 5 + hash,这样比乘法速度更快。从这个函数可以看出来PHP鼓励hash字符串的长度小于等于8位,一般也不会有人把key的长度设置的超过8位吧。说白了就是以空间换时间,哈希的字符串长度大于8位时一次for循环就执行了8次hash

    hash的初始值设置成了5381, 相比在Apache中的times算法和Perl中的Hash算法(都采用初始hash为0), 为什么是5381?
    这是个神奇的数字,集素数、奇数、缺数为一身,而且它的二进制也很独特。在测试中,5381可以导致哈希碰撞更少,避免雪崩。

    case后面的常量表达式实际上只起语句标号作用,而不起条件判断作用,即"只是开始执行处的入口标号". 因此,一旦与switch后面圆括号中表达式的值匹配,就从此标号处开始执行,而且执行完一个case后面的语句后,若没遇到break语句,就自动进入 下一个case继续执行,而不在判断是否与之匹配,直到遇到break语句才停止执行,退出break语句.因此,若想执行一个case分之后立即跳出 switch语句,就必须在此分支的最后添加一个break语句.

     

    HashTable的大小,始终为2的指数(8,16,32,64...)。最小为8,最大值根据机器不同而不同
  • 相关阅读:
    Java中如何实现序列化,有什么意义?
    java中this和super关键字的作用
    java中String类的面试题大全含答案
    java中static关键字的作用
    final和abstract关键字的作用
    Java.util.Map的实现类有那些?
    java.sql.Date和java.util.Date的联系和区别
    Java 的信号灯
    java.lang.ThreadLocal的作用和原理?列举在哪些程序中见过ThreadLocal的使用?
    HashMap是不是有序的?
  • 原文地址:https://www.cnblogs.com/taek/p/5463829.html
Copyright © 2020-2023  润新知