제목은 루프 본문에 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('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'); if ($bytes === 0) { return '0 ' . $unit[0]; } return @round($bytes / (1024 ** ($i = floor(log($bytes, 1024)))), 2) . ' ' . ($unit[(int)$i] ?? 'B'); } $unit = array('B', 'KB', 'MB', 'GB', 'TB', 'PB'); if ($bytes === 0) { return '0 ' . $unit[0]; } return @round($bytes / (1000 ** ($i = floor(log($bytes, 1000)))), 2) . ' ' . ($unit[(int)$i] ?? 'B'); } }
을 사용하여 더 많은 메모리를 먼저 할당합니다.
메모리 사용량, 병합된 배열을 출력합니다.
ini_set('memory_limit', '4000M'); $timeOne = microtime(true); $a = ArrayMerge::eachOne(10000); echo 'count eachOne Result | ' . count($a) . PHP_EOL; echo 'memory eachOne Result | ' . ArrayMerge::getNiceFileSize(memory_get_usage(true)) . PHP_EOL; $timeTwo = microtime(true); $b = ArrayMerge::eachTwo(10000); echo 'count eachTwo Result | ' . count($b) . PHP_EOL; echo 'memory eachTwo Result | ' . ArrayMerge::getNiceFileSize(memory_get_usage(true)) . PHP_EOL; $timeThree = microtime(true); $c = ArrayMerge::eachThree(10000); echo 'count eachThree Result | ' . count($c) . PHP_EOL; echo 'memory eachThree Result | ' . ArrayMerge::getNiceFileSize(memory_get_usage(true)) . PHP_EOL; $timeFour = microtime(true); $d = ArrayMerge::eachFour(10000); echo 'count eachFour Result | ' . count($d) . PHP_EOL; echo 'memory eachFour Result | ' . ArrayMerge::getNiceFileSize(memory_get_usage(true)) . PHP_EOL; $timeFive = microtime(true); echo PHP_EOL; echo 'eachOne | ' . ($timeTwo - $timeOne) . PHP_EOL; echo 'eachTwo | ' . ($timeThree - $timeTwo) . PHP_EOL; echo 'eachThree | ' . ($timeFour - $timeThree) . PHP_EOL; echo 'eachFour | ' . ($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 = [ '111' => 'testA1', 'abc' => 'testA1', '222' => 'testA2', ]; $testB = [ '111' => 'testB1', 'abc' => 'testB1', '222' => 'testB2', 'www' => 'testB1', ]; echo 'array_merge($testA, $testB) | ' . PHP_EOL; print_r(array_merge($testA, $testB)); echo '$testA + $testB | ' . PHP_EOL; print_r($testA + $testB); echo '$testB + $testA | ' . PHP_EOL; print_r($testB + $testA); echo 'array_merge_recursive($testA, $testB) | ' . 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 $... ] ) : arrayarray_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 중국어 웹사이트의 기타 관련 기사를 참조하세요!