>  기사  >  백엔드 개발  >  루프 본문 내에서 array_merge()를 사용하지 마세요.

루프 본문 내에서 array_merge()를 사용하지 마세요.

藏色散人
藏色散人앞으로
2019-10-30 14:02:372778검색

제목은 루프 본문에 array_merge()를 사용하지 마십시오입니다. 사실 이것은 이 기사의 결론 중 하나일 뿐입니다.

PHP 언어에서 배열 병합을 연구해 보겠습니다(여기서는 재귀 병합을 고려하지 않습니다)

네 가지 종류의 병합 배열 방법 비교

네 가지 일반적인 배열 병합 방법 비교

코드 작성

우리는 array_merge()와 연산자 + 모두 배열을 연결할 수 있다는 것을 알고 있습니다

클래스 만들기

ArrayMerge()
● EachOne() 루프 본문은 array_merge()를 사용하여 병합
● EachTwo() 루프 본문이 끝난 후 array_merge()를 사용하여 병합
● EachThree() 루프 본문은 배열 병합을 구현하기 위해 중첩됩니다
● EachFour() 루프 본문은 연산자 + 접합 및 병합을 사용합니다.
● getNiceFileSize()는 메모리 사용량을 사람이 읽을 수 있는 형식으로 변환합니다.

/**
 * Class ArrayMerge
 */
class ArrayMerge
{
    /**
     * @param int $times
     * @return array
     */
    public static function eachOne(int $times): array
    {
        $a = [];
        $b = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
        for ($i = 0; $i < $times; $i++) {
            $a = array_merge($a, $b);
        }
        return $a;
    }
    /**
     * @param int $times
     * @return array
     */
    public static function eachTwo(int $times): array
    {
        $a = [[]];
        $b = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
        for ($i = 0; $i < $times; $i++) {
            $a[] = $b;
        }
        return array_merge(...$a);
    }
    /**
     * @param int $times
     * @return array
     */
    public static function eachThree(int $times): array
    {
        $a = [];
        $b = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
        for ($i = 0; $i < $times; $i++) {
            foreach ($b as $item) {
                $a[] = $item;
            }
        }
        return $a;
    }
    /**
     * @param int $times
     * @return array
     */
    public static function eachFour(int $times): array
    {
        $a = [];
        $b = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
        for ($i = 0; $i < $times; $i++) {
            $a = $b + $a;
        }
        return $a;
    }
    /**
     * 转化内存信息
     * @param      $bytes
     * @param bool $binaryPrefix
     * @return string
     */
    public static function getNiceFileSize(int $bytes, $binaryPrefix = true): ?string
    {
        if ($binaryPrefix) {
            $unit = array(&#39;B&#39;, &#39;KiB&#39;, &#39;MiB&#39;, &#39;GiB&#39;, &#39;TiB&#39;, &#39;PiB&#39;);
            if ($bytes === 0) {
                return &#39;0 &#39; . $unit[0];
            }
            return @round($bytes / (1024 ** ($i = floor(log($bytes, 1024)))),
                    2) . &#39; &#39; . ($unit[(int)$i] ?? &#39;B&#39;);
        }
        $unit = array(&#39;B&#39;, &#39;KB&#39;, &#39;MB&#39;, &#39;GB&#39;, &#39;TB&#39;, &#39;PB&#39;);
        if ($bytes === 0) {
            return &#39;0 &#39; . $unit[0];
        }
        return @round($bytes / (1000 ** ($i = floor(log($bytes, 1000)))),
                2) . &#39; &#39; . ($unit[(int)$i] ?? &#39;B&#39;);
    }
}

을 사용하여 더 많은 메모리를 먼저 할당합니다.

메모리 사용량, 병합된 배열을 출력합니다.

ini_set(&#39;memory_limit&#39;, &#39;4000M&#39;);
$timeOne = microtime(true);
$a       = ArrayMerge::eachOne(10000);
echo &#39;count eachOne Result | &#39; . count($a) . PHP_EOL;
echo &#39;memory eachOne Result | &#39; . ArrayMerge::getNiceFileSize(memory_get_usage(true)) . PHP_EOL;
$timeTwo = microtime(true);
$b       = ArrayMerge::eachTwo(10000);
echo &#39;count eachTwo Result | &#39; . count($b) . PHP_EOL;
echo &#39;memory eachTwo Result | &#39; . ArrayMerge::getNiceFileSize(memory_get_usage(true)) . PHP_EOL;
$timeThree = microtime(true);
$c         = ArrayMerge::eachThree(10000);
echo &#39;count eachThree Result | &#39; . count($c) . PHP_EOL;
echo &#39;memory eachThree Result | &#39; . ArrayMerge::getNiceFileSize(memory_get_usage(true)) . PHP_EOL;
$timeFour = microtime(true);
$d        = ArrayMerge::eachFour(10000);
echo &#39;count eachFour Result | &#39; . count($d) . PHP_EOL;
echo &#39;memory eachFour Result | &#39; . ArrayMerge::getNiceFileSize(memory_get_usage(true)) . PHP_EOL;
$timeFive = microtime(true);
echo PHP_EOL;
echo &#39;eachOne | &#39; . ($timeTwo - $timeOne) . PHP_EOL;
echo &#39;eachTwo | &#39; . ($timeThree - $timeTwo) . PHP_EOL;
echo &#39;eachThree | &#39; . ($timeFour - $timeThree) . PHP_EOL;
echo &#39;eachFour | &#39; . ($timeFive - $timeFour) . PHP_EOL;
echo PHP_EOL;

결과

count eachOne Result | 100000
memory eachOne Result | 9 MiB
count eachTwo Result | 100000
memory eachTwo Result | 14 MiB
count eachThree Result | 100000
memory eachThree Result | 18 MiB
count eachFour Result | 10           #注意这里
memory eachFour Result | 18 MiB
eachOne | 5.21253490448                 # 循环体中使用array_merge()最慢,而且耗费内存
eachTwo | 0.0071840286254883            # 循环体结束后使用array_merge()最快
eachThree | 0.037622928619385           # 循环体嵌套比循环体结束后使用array_merge()慢三倍
eachFour | 0.0072360038757324           # 看似也很快,但是合并的结果有问题

● 루프 본문에서 array_merge()를 사용하는 것이 가장 느리고 메모리를 소모합니다

● 루프 본문 다음에 array_merge()를 사용하는 것이 가장 빠릅니다

● 루프 본문을 중첩하는 것은 루프 본문이 끝난 후 array_merge()를 사용하는 것보다 3배 느립니다. 시간

● 매우 빠른 것 같지만 병합된 결과에 문제가 있습니다

배열 병합의 함정

우리는 다음을 발견했습니다. 지금은 각각의 Four의 결과 길이가 10에 불과합니다.

왜 그런 결과가 발생하는지 살펴보겠습니다.

여기에서 두 개의 배열을 연결하여 재귀 병합 코드

public static function test(): void
{
    $testA = [
        &#39;111&#39; => &#39;testA1&#39;,
        &#39;abc&#39; => &#39;testA1&#39;,
        &#39;222&#39; => &#39;testA2&#39;,
    ];
    $testB = [
        &#39;111&#39; => &#39;testB1&#39;,
        &#39;abc&#39; => &#39;testB1&#39;,
        &#39;222&#39; => &#39;testB2&#39;,
        &#39;www&#39; => &#39;testB1&#39;,
    ];
    echo &#39;array_merge($testA, $testB) | &#39; . PHP_EOL;
    print_r(array_merge($testA, $testB));
    echo &#39;$testA + $testB | &#39; . PHP_EOL;
    print_r($testA + $testB);
    echo &#39;$testB + $testA | &#39; . PHP_EOL;
    print_r($testB + $testA);
    echo &#39;array_merge_recursive($testA, $testB) | &#39; . PHP_EOL;
    print_r(array_merge_recursive($testA, $testB));
}
결과

+를 비교해 보겠습니다. 후자는 단지 보충만 할 것입니다. 전자에는 없지만 숫자 인덱스를 유지하는 키

array_merge() 및 array_merge_recursive( )는 숫자 인덱스를 지우고 모든 숫자 인덱스는 0부터 순서대로 시작합니다

array_merge($testA, $testB) |    #数字索引强制从0开始了 字符key相同的以后者为准
Array
(
    [0] => testA1
    [abc] => testB1
    [1] => testA2
    [2] => testB1
    [3] => testB2
    [www] => testB1
)
$testA + $testB |        #testA得到保留,testB补充了testA中没有的key,数字索引得到保留
Array
(
    [111] => testA1
    [abc] => testA1
    [222] => testA2
    [www] => testB1
)
$testB + $testA |        #testB得到保留,testA补充了testB中没有的key,数字索引得到保留
Array
(
    [111] => testB1
    [abc] => testB1
    [222] => testB2
    [www] => testB1
)

array_merge_recursive($testA, $testB) | #숫자 인덱스는 연속적으로 0부터 시작하지만 배열의 순서는 깨지지 않고, 같은 문자열 `key`가 배열로 병합됩니다

Array
(
    [0] => testA1
    [abc] => Array
        (
            [0] => testA1
            [1] => testB1
        )
    [1] => testA2
    [2] => testB1
    [3] => testB2
    [www] => testB1
)

Analytic

이거 보면 많이 헷갈리실 텐데요. array_merge()에 그런 함정이 있을 거라고는 기대하지 마세요

먼저 공식 매뉴얼을 살펴보겠습니다

array_merge ( array $array1 [, array $... ] ) : array

array_merge()는 하나 이상의 배열 요소를 이전 배열에 추가된 한 배열의 값과 결합합니다. 결과 배열을 반환합니다.

입력 배열의 문자열 키 이름이 동일한 경우 키 이름 뒤의 값이 이전 값을 덮어씁니다. 그러나 배열에 숫자 키가 포함된 경우 후속 값은 원래 값을 덮어쓰지 않고 추가됩니다.

배열만 지정하고 해당 배열을 숫자로 인덱싱하는 경우 키 이름은 연속적으로 다시 인덱싱됩니다.

문자열 키 이름이 동일한 경우에만 나중 값이 이전 값을 덮어씁니다. (단, 매뉴얼에는 숫자키 이름의 인덱스가 재설정되는 이유가 설명되어 있지 않습니다.)

그럼 소스코드를 살펴보겠습니다

PHPAPI int php_array_merge(HashTable *dest, HashTable *src)
{
    zval *src_entry;
    zend_string *string_key;
    if ((dest->u.flags & HASH_FLAG_PACKED) && (src->u.flags & HASH_FLAG_PACKED)) {
        // 自然数组的合并,HASH_FLAG_PACKED表示数组是自然数组([0,1,2])   参考http://ju.outofmemory.cn/entry/197064
        zend_hash_extend(dest, zend_hash_num_elements(dest) + zend_hash_num_elements(src), 1);
        ZEND_HASH_FILL_PACKED(dest) {
            ZEND_HASH_FOREACH_VAL(src, src_entry) {
                if (UNEXPECTED(Z_ISREF_P(src_entry)) &&
                    UNEXPECTED(Z_REFCOUNT_P(src_entry) == 1)) {
                    ZVAL_UNREF(src_entry);
                }
                Z_TRY_ADDREF_P(src_entry);
                ZEND_HASH_FILL_ADD(src_entry);
            } ZEND_HASH_FOREACH_END();
        } ZEND_HASH_FILL_END();
    } else {
        //遍历获取key和vaule
        ZEND_HASH_FOREACH_STR_KEY_VAL(src, string_key, src_entry) {
            if (UNEXPECTED(Z_ISREF_P(src_entry) &&
                Z_REFCOUNT_P(src_entry) == 1)) {
                ZVAL_UNREF(src_entry);
            }
            Z_TRY_ADDREF_P(src_entry);
            //  参考https://github.com/pangudashu/php7-internal/blob/master/7/var.md
            if (string_key) {
                // 字符串key(zend_string)  插入或者更新元素,会增加key的计数
                zend_hash_update(dest, string_key, src_entry);
            } else {
                //插入新元素,使用自动的索引值(破案了,索引被重置的原因在此)
                zend_hash_next_index_insert_new(dest, src_entry);
            }
        } ZEND_HASH_FOREACH_END();
    }
    return 1;
}

요약

요약하자면, 배열을 병합하는 다양한 방법에는 특정한 결함이 있지만, 위의 탐색을 통해 우리는

● 루프 본문에서 배열을 병합하기 위해 array_merge()를 사용하는 것은 바람직하지 않으며 속도 차이가 최대 100배라는 것을 알게 되었습니다.

● array_merge()는 다음과 같은 경우 주의해서 사용해야 합니다. 키에 주의를 기울이면 키가 숫자일 수 있으므로 Array_merge()를 사용하여 병합할 수 없습니다. 중첩 루프를 사용할 수 있습니다(내부 루프는 할당을 위해 키를 사용합니다)

● 키와 키는 숫자일 수 있으므로 연산자 +를 사용하여 간단히 배열을 병합할 수 있습니다. 그러나 각 작업의 결과는 새로운 배열이므로 루프 본문에 사용하지 마십시오

위 내용은 루프 본문 내에서 array_merge()를 사용하지 마세요.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 learnku.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제