PHP的count(数组)和strlen(字符串)的内部实现。

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PHP的count(数组)和strlen(字符串)的内部实现。相关的知识,希望对你有一定的参考价值。

PHP的count(数组)和strlen(字符串)的内部实现上是直接显示一个长度变量,还是重头依次数一遍有多少个元素?
关乎我理解这2个函数的效率。。
希望高人能从php的c源码上讲一讲。没有源码看过源码知道的说说也行。
1、count执行时背后有没有“逐个统计”子元素的个数?
2、strlen执行时背后有没有“逐个统计”字符的个数?
==========
我已经找到PHP strlen的源码了,唉沉下去的没几人。。。
正在找count的
=================
结论
strlen直接返回长度变量,没有计算。效率O(1)
count逐个数元素,发生大量计算。效率O(N)
=======
自己查出的答案。。。问题终结。
谢谢关注

翻了下PHP内核的定义,大概心中也有了答案了

count()和strlen()都是O(1)的时间复杂度

试想一下如果strlen()需要O(N)的复杂度那未免也太慢了,字符串长度起来的话服务器不是要直接挂掉吗

这两个函数都是典型的空间换时间的做法

我们可以先看看zvalue的结构:

typedef union _zvalue_value 
   long lval;             /* long value */
   double dval;            /* double value */
   struct 
      char *val;
      int len;
    str;
   HashTable *ht;          /* hash table value */
   zend_object_value obj;
   zend_ast *ast;
 zvalue_value;

这里用的是一个联合体,当变量类型是string类型的时候附加存储多了一个len的整型变量,显而易见需要取长度直接利用记录值就可以了,自然就是O(1)

对于count()常用的参数类型应该为数组,对于继承Countable的类暂不作讨论

数组实现方式为Hashtable,直接看看他的结构吧

typedef struct _hashtable  
    uint nTableSize;        // hash Bucket的大小,最小为8,以2x增长。
    uint nTableMask;        // nTableSize-1 , 索引取值的优化
    uint nNumOfElements;    // hash Bucket中当前存在的元素个数,count()函数会直接返回此值 
    ulong nNextFreeElement; // 下一个数字索引的位置
    Bucket *pInternalPointer;   // 当前遍历的指针(foreach比for快的原因之一)
    Bucket *pListHead;          // 存储数组头元素指针
    Bucket *pListTail;          // 存储数组尾元素指针
    Bucket **arBuckets;         // 存储hash数组
    dtor_func_t pDestructor;    // 在删除元素时执行的回调函数,用于资源的释放
    zend_bool persistent;       //指出了Bucket内存分配的方式。如果persisient为TRUE,则使用操作系统本身的内存分配函数为Bucket分配内存,否则使用PHP的内存分配函数。
    unsigned char nApplyCount; // 标记当前hash Bucket被递归访问的次数(防止多次递归)
    zend_bool bApplyProtection;// 标记当前hash桶允许不允许多次访问,不允许时,最多只能递归3次
#if ZEND_DEBUG
    int inconsistent;
#endif
 HashTable;

count直接获取nNumOfElements大小,所以也是O(1)

补充------------------------------------------------

count() 函数的定义在这里

/*  proto int count(mixed var [, int mode])
   Count the number of elements in a variable (usually an array) */
PHP_FUNCTION(count)

    zval *array;
    zend_long mode = COUNT_NORMAL;
    zend_long cnt;
    zval *element;

    ZEND_PARSE_PARAMETERS_START(1, 2)
        Z_PARAM_ZVAL(array)
        Z_PARAM_OPTIONAL
        Z_PARAM_LONG(mode)
    ZEND_PARSE_PARAMETERS_END();

    switch (Z_TYPE_P(array)) 
        case IS_NULL:
            php_error_docref(NULL, E_WARNING, "Parameter must be an array or an object that implements Countable");
            RETURN_LONG(0);
            break;
        case IS_ARRAY:
            if (mode != COUNT_RECURSIVE) 
                //类型为数组时调用zend内核函数 zend_array_count()
                cnt = zend_array_count(Z_ARRVAL_P(array));
             else 
                cnt = php_count_recursive(Z_ARRVAL_P(array));
            
            RETURN_LONG(cnt);
            break;
        case IS_OBJECT: 
            zval retval;
            /* first, we check if the handler is defined */
            if (Z_OBJ_HT_P(array)->count_elements) 
                RETVAL_LONG(1);
                if (SUCCESS == Z_OBJ_HT(*array)->count_elements(array, &Z_LVAL_P(return_value))) 
                    return;
                
            
            /* if not and the object implements Countable we call its count() method */
            if (instanceof_function(Z_OBJCE_P(array), zend_ce_countable)) 
                zend_call_method_with_0_params(array, NULL, NULL, "count", &retval);
                if (Z_TYPE(retval) != IS_UNDEF) 
                    RETVAL_LONG(zval_get_long(&retval));
                    zval_ptr_dtor(&retval);
                
                return;
            

            /* If There's no handler and it doesn't implement Countable then add a warning */
            php_error_docref(NULL, E_WARNING, "Parameter must be an array or an object that implements Countable");
            RETURN_LONG(1);
            break;
        
        default:
            php_error_docref(NULL, E_WARNING, "Parameter must be an array or an object that implements Countable");
            RETURN_LONG(1);
            break;
    

如果没有特别指定mode参数为 COUNT_RECURSIVE 的话(即作遍历),跳转到 zend 的数组计数函数 zend_array_count()

#define zend_hash_num_elements(ht) \\
    (ht)->nNumOfElements

...
...

static uint32_t zend_array_recalc_elements(HashTable *ht)

    zval *val;
    uint32_t num = ht->nNumOfElements;
    
           ZEND_HASH_FOREACH_VAL(ht, val) 
               if (Z_TYPE_P(val) == IS_INDIRECT) 
                   if (UNEXPECTED(Z_TYPE_P(Z_INDIRECT_P(val)) == IS_UNDEF)) 
                       num--;
                   
               
     ZEND_HASH_FOREACH_END();
    return num;


ZEND_API uint32_t zend_array_count(HashTable *ht)

    uint32_t num;
    if (UNEXPECTED(ht->u.v.flags & HASH_FLAG_HAS_EMPTY_IND)) 
        num = zend_array_recalc_elements(ht);
        if (UNEXPECTED(ht->nNumOfElements == num)) 
            ht->u.v.flags &= ~HASH_FLAG_HAS_EMPTY_IND;
        
     else if (UNEXPECTED(ht == &EG(symbol_table))) 
        num = zend_array_recalc_elements(ht);
     else 
        num = zend_hash_num_elements(ht);
    
    return num;

IS_REFERENCE:间接 zval 指的就是其真正的值是存储在其他地方的。注意这个 IS_REFERENCE 类型是不同的,间接 zval 是直接指向另外一个 zval 而不是像 zend_reference 结构体一样嵌入 zval。

只有当数组中有 HASH_FLAG_HAS_EMPTY_IND 这个 flag 时(间接zval)才会对数组进行遍历校验,其他情况下都是直接取 数组(hash table) 里面的 nNumOfElements 的值,答案显而易见了,就是O(1)

参考技术A 我觉得这两个函数是两码事:
count是针对数组的,返回的是数组的长度,在实现上,数组就是一张hash表,count的时候就是统计这张表的元素个数
strlen是针对字符串的,返回的是字符串的长度,也就是字符串中的字符的个数,在实现上,没看过不清楚。
在使用上,strlen是不能用来统计数组的个数的,
虽然count可以传入字符串作为参数,但是它返回的是相当于count((array)字符串) 也就是 1 的效果。 和strlen返回的意义就不一样了追问

先读懂我的问题吧,我没有问PHP层面的功能,功能在手册上都写着。问的是
1、count执行时背后有没有逐个统计“子元素”的个数?
2、strlen执行时背后有没有逐个统计“字符”的个数?
题意是把这2句合在一起问的。

以上是关于PHP的count(数组)和strlen(字符串)的内部实现。的主要内容,如果未能解决你的问题,请参考以下文章

php 字符串

php中获得数组长度的方法

在 PHP 中使用 substr_count() 和数组

php 常用的系统函数

PHP 计算两个特别大的整数

PHP中strlen和mb_strlen函数的区别