当前位置 博文首页 > 文章内容

    不要在循环体中使用 array_push ()

    作者:shunshunshun18 栏目:未分类 时间:2021-08-29 10:51:46

    本站于2023年9月4日。收到“大连君*****咨询有限公司”通知
    说我们IIS7站长博客,有一篇博文用了他们的图片。
    要求我们给他们一张图片6000元。要不然法院告我们

    为避免不必要的麻烦,IIS7站长博客,全站内容图片下架、并积极应诉
    博文内容全部不再显示,请需要相关资讯的站长朋友到必应搜索。谢谢!

    另祝:版权碰瓷诈骗团伙,早日弃暗投明。

    相关新闻:借版权之名、行诈骗之实,周某因犯诈骗罪被判处有期徒刑十一年六个月

    叹!百花齐放的时代,渐行渐远!



    标题是不要在循环体中使用 array_push(),其实这只是本篇文章的结论之一

    下面我们一起研究一下 php 语言中数组的追加元素

    向数组追加元素

    我们知道 php 在数组栈尾追加元素的方式有两种

    $a = []; array_push($a,'test');
    $a[] = 'test';

    那么这两种方式有什么区别呢?

    我们先来比较一下性能

    ArrayPush
    一个 ArrayPush 类
    pushEachOne() 循环体中使用 array_push() 来为 $a 追加元素
    pushEachTwo() 循环体中使用 $a[] = $var 来为 $a 追加元素
    /**
     * Class ArrayPush
     */
    class ArrayPush
    {
        /**
         * @param int $times
         * @return array
         */
        public static function pushEachOne(int $times): array
        {
            $a = [];
            $b = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
            for ($i = 0; $i < $times; $i++) {
                array_push($a, $b[$i % 10]);
            }
            return $a;
        }
        /**
         * @param int $times
         * @return array
         */
        public static function pushEachTwo(int $times): array
        {
            $a = [];
            $b = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
            for ($i = 0; $i < $times; $i++) {
                $a[] = $b[$i % 10];
            }
            return $a;
        }
    }

    编写代码测试

    循环追加 100 万个元素

    ini_set('memory_limit', '4000M');
    $timeOne = microtime(true);
    $a       = ArrayPush::pushEachOne(1000000);
    echo 'count pushEachOne result | ' . count($a) . PHP_EOL;
    $timeTwo = microtime(true);
    $b       = ArrayPush::pushEachTwo(1000000);
    echo 'count pushEachTwo result | ' . count($b) . PHP_EOL;
    $timeThree = microtime(true);
    echo PHP_EOL;
    echo 'pushEachOne | ' . ($timeTwo - $timeOne) . PHP_EOL;
    echo 'pushEachTwo | ' . ($timeThree - $timeTwo) . PHP_EOL;
    echo PHP_EOL;

    结果

    结果不言而喻,$a[] = 比使用 array_push() 快了接近三倍

    count pushEachOne result | 1000000
    count pushEachTwo result | 1000000
    pushEachOne | 1.757071018219
    pushEachTwo | 0.67165303230286

    分析

    array_push () 为什么慢?这么慢,我们还有使用它的场景吗?

    官方手册

    array_push — 将一个或多个单元压入数组的末尾(入栈)
    array_push ( array &$array , mixed $value1 [, mixed $... ] ) : int
    array_push() 将 array 当成一个栈,并将传入的变量压入 array 的末尾。array 的长度将根据入栈变量的数目增加。和如下效果相同:
    <?php$array[] = $var;?>

    并对每个传入的值重复以上动作。

    ● Note: 如果用 array_push() 来给数组增加一个单元,还不如用 \$array[] = ,因为这样没有调用函数的额外负担。

    ● Note: 如果第一个参数不是数组,array_push() 将发出一条警告。这和 \$var[] 的行为不同,后者会新建一个数组。

    官方源码

    看一下源码中的 array_push()

    /* {{{ proto int array_push(array stack, mixed var [, mixed ...])
       Pushes elements onto the end of the array */
    PHP_FUNCTION(array_push)
    {
        zval   *args,       /* Function arguments array */
               *stack,      /* Input array */
                new_var;    /* Variable to be pushed */
        int i,              /* Loop counter */
            argc;           /* Number of function arguments */
        //这一段是函数的参数解析
        ZEND_PARSE_PARAMETERS_START(2, -1)
            Z_PARAM_ARRAY_EX(stack, 0, 1)
            Z_PARAM_VARIADIC('+', args, argc)
        ZEND_PARSE_PARAMETERS_END();
        /* For each subsequent argument, make it a reference, increase refcount, and add it to the end of the array */
        for (i = 0; i < argc; i++) {
            //拷贝一个
            ZVAL_COPY(&new_var, &args[i]);
            //插入新数值,自动
            if (zend_hash_next_index_insert(Z_ARRVAL_P(stack), &new_var) == NULL) {
                if (Z_REFCOUNTED(new_var)) Z_DELREF(new_var);
                php_error_docref(NULL, E_WARNING, "Cannot add element to the array as the next element is already occupied");
                RETURN_FALSE;
            }
        }
        /* Clean up and return the number of values in the stack */
        RETVAL_LONG(zend_hash_num_elements(Z_ARRVAL_P(stack)));
    }
    /* }}} */

    $a[] = 的实现是根据赋值的变量类型调用了一系列 Zend_API 函数 add_next_index_* ,它们在设置一个对应类型的 zval 值以后直接调用了 zend_hash_next_index_insert

    ZEND_API int add_next_index_long(zval *arg, zend_long n) /* {{{ */
    {
        zval tmp;
        ZVAL_LONG(&tmp, n);
        return zend_hash_next_index_insert(Z_ARRVAL_P(arg), &tmp) ? SUCCESS : FAILURE;
    }
    /* }}} */
    ZEND_API int add_next_index_null(zval *arg) /* {{{ */
    {
        zval tmp;
        ZVAL_NULL(&tmp);
        return zend_hash_next_index_insert(Z_ARRVAL_P(arg), &tmp) ? SUCCESS : FAILURE;
    }
    /* }}} */
    ZEND_API int add_next_index_bool(zval *arg, int b) /* {{{ */
    {
        zval tmp;
        ZVAL_BOOL(&tmp, b);
        return zend_hash_next_index_insert(Z_ARRVAL_P(arg), &tmp) ? SUCCESS : FAILURE;
    }
    /* }}} */
    ZEND_API int add_next_index_resource(zval *arg, zend_resource *r) /* {{{ */
    {
        zval tmp;
        ZVAL_RES(&tmp, r);
        return zend_hash_next_index_insert(Z_ARRVAL_P(arg), &tmp) ? SUCCESS : FAILURE;
    }
    /* }}} */
    ZEND_API int add_next_index_double(zval *arg, double d) /* {{{ */
    {
        zval tmp;
        ZVAL_DOUBLE(&tmp, d);
        return zend_hash_next_index_insert(Z_ARRVAL_P(arg), &tmp) ? SUCCESS : FAILURE;
    }
    /* }}} */
    ZEND_API int add_next_index_str(zval *arg, zend_string *str) /* {{{ */
    {
        zval tmp;
        ZVAL_STR(&tmp, str);
        return zend_hash_next_index_insert(Z_ARRVAL_P(arg), &tmp) ? SUCCESS : FAILURE;
    }
    /* }}} */
    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;
    }
    /* }}} */
    ZEND_API int add_next_index_stringl(zval *arg, const char *str, size_t length) /* {{{ */
    {
        zval tmp;
        ZVAL_STRINGL(&tmp, str, length);
        return zend_hash_next_index_insert(Z_ARRVAL_P(arg), &tmp) ? SUCCESS : FAILURE;
    }
    /* }}} */
    ZEND_API int add_next_index_zval(zval *arg, zval *value) /* {{{ */
    {
        return zend_hash_next_index_insert(Z_ARRVAL_P(arg), value) ? SUCCESS : FAILURE;
    }
    /* }}} */

    总结

    经过上面的分析,仿佛 array_push() 没有任何存在的意义,真的是这样吗?

    ● 一般情况下,array_push() 性能太差,所以我们应当使用 $array[] = 来替换掉它

    ● 如果一次追加多个单元,使用 array_push()