需求:
1个长库10万级别的二维数组,元素内数组长度10个左右(其实就是一个数据表的结果集合),根据指定字段对数据进行去重,最后要得到去重后被丢弃的数据明细。
两个关键过程:
过程1 - 根据指定字段对数组内元素进行去重:
function arrayUniqueByKey(array $arr, string $key): array { $arr = array_reverse($arr); // 数组倒置保留重复的第1个元素,如果不倒置就是保留重复的最后1个元素 if (isset($arr[0][$key])) { foreach ($arr as $v) { $newArr[$v[$key]] = $v; } } $newArr = isset($newArr) ? array_values($newArr) : []; $newArr = array_reverse($newArr); // 前面倒置了,后面就再给倒回去 return $newArr; }
简单说明:遍历1次数组是必须的,遍历过程中只是拿关键字段的值当作键名去创建一个新数组,这样当关键字段重复时,赋值就相当于覆盖,进而实现去重。
过程2 - 求原数组和被去重后数组的差集:
function arrayDiff(array $arr1, array $arr2): array { $arr1 = array_map(function ($v) { return json_encode($v); }, $arr1); $arr2 = array_map(function ($v) { return json_encode($v); }, $arr2); $diff = array_diff($arr1, $arr2); $diff = array_map(function ($v) { return json_decode($v, true); }, $diff); return array_values($diff); }
简单说明:先把两个多维数组转成一维数组,再用array_diff()求得一维数组差集,最后把一维差集再转回多维。
模拟测试:
<?php /** * 得到系统当前毫秒级时间的各种表示 * * @author Aaron <[email protected]> */ function getSystemTimes() { list($usec, $sec) = explode(" ", microtime()); $microtime = ((float)$usec + (float)$sec); $time = (int)floor($microtime); $millsecond = round($microtime - $time, 3) * 1000; $millsecond = str_pad($millsecond, 3, '0', STR_PAD_LEFT); $weekarray = array("日", "一", "二", "三", "四", "五", "六"); $data['year'] = date('Y', $time); $data['month'] = date('m', $time); $data['day'] = date('d', $time); $data['hour'] = date('H', $time); $data['hour_pre'] = date('A', $time); $data['hour_12'] = date('h', $time); $data['minute'] = date('i', $time); $data['second'] = date('s', $time); $data['millisecond'] = $millsecond; $data['date'] = date('Y-m-d', $time); $data['time'] = date('H:i:s', $time); $data['weekday'] = "星期{$weekarray[date('w',$time)]}"; $data['weekday_index'] = date('w', $time); $data['time_milli'] = "{$data['time']}.{$millsecond}"; $data['datetime'] = date('Y-m-d H:i:s', $time); $data['datetime_milli'] = "{$data['datetime']}.{$millsecond}"; $data['timestamp'] = $time; $data['timestamp_micro'] = $microtime; return $data; } /** * 二维数组针对指定键名去重 * * @author Aaron <[email protected]> * * @param array $arr * @param string $key * * @return array */ function arrayUniqueByKey(array $arr, string $key): array { $arr = array_reverse($arr); // 数组倒置保留重复的第1个元素,如果不倒置就是保留重复的最后1个元素 if (isset($arr[0][$key])) { foreach ($arr as $v) { $newArr[$v[$key]] = $v; } } $newArr = isset($newArr) ? array_values($newArr) : []; $newArr = array_reverse($newArr); // 前面倒置了,后面就再给倒回去 return $newArr; } /** * 求两个多维数组的差集(两个参数别弄反了) * * @author Aaron <[email protected]> * * @param array $arr1 * @param array $arr2 * * @return array 返回$arr1-$arr2的结果 */ function arrayDiff(array $arr1, array $arr2): array { $arr1 = array_map(function ($v) { return json_encode($v); }, $arr1); $arr2 = array_map(function ($v) { return json_encode($v); }, $arr2); $diff = array_diff($arr1, $arr2); $diff = array_map(function ($v) { return json_decode($v, true); }, $diff); return array_values($diff); } $arr = []; echo "\n生成数组开始: " . getSystemTimes()['datetime_milli']; for ($i = 0; $i < 99999; $i++) { $index = $i; if ($i % 101 == 0) { $index = $i - 1; } $suffix = str_pad($index, 8, '0', STR_PAD_LEFT); if ($index % 113 == 0) { $suffix = str_pad($index - 1, 8, '0', STR_PAD_LEFT); } $arr[] = [ 'name' => '优优' . $index, 'phone' => "133{$suffix}", 'var1' => $index, 'var2' => '', 'var3' => '', 'var4' => '', 'var5' => $index, ]; } echo "\n生成数组完成: " . getSystemTimes()['datetime_milli']; echo "\n键名去重开始: " . getSystemTimes()['datetime_milli']; $arr2 = arrayUniqueByKey($arr, 'phone'); echo "\n键名去重完成: " . getSystemTimes()['datetime_milli']; echo "\n去重求差开始: " . getSystemTimes()['datetime_milli']; $diff = arrayDiff($arr, $arr2); echo "\n去重求差完成: " . getSystemTimes()['datetime_milli']; echo "\n"; var_dump(count($diff));
运行结果:
php temp.php 生成数组开始: 2022-08-19 10:08:54.975 生成数组完成: 2022-08-19 10:08:55.335 键名去重开始: 2022-08-19 10:08:55.335 键名去重完成: 2022-08-19 10:08:55.643 去重求差开始: 2022-08-19 10:08:55.643 去重求差完成: 2022-08-19 10:08:56.320 int(876)
从结果可以看到,数据量是99999,生成模拟数据大概花了400ms,去重花的时间大概是300ms,求差集花的时间大概是700ms,去重+求差大约在1秒内完成,这个时效对于这个数据量来说还是可以接受的。
总结:
原生数组操作函数的性能一般都是很高的,能用原生就尽量用原生。
用array_map加工数组元素的时间效率很高;
用array_diff求数组差集的时间效率很高;
用array_reverse得到倒置数组的时间效率很高;
其他:
对多维数组求差过程特别说明一下,有人说用array_filter和in_array去实现,看上去代码很简洁,但是实际测试时发现性能极差,那段代码是下面这样的,感兴趣可自行测试:
$r = array_filter($arr1, function ($v) use ($arr2) { return !in_array($v, $arr2); }); 或者 foreach ($all_wechat as $k => $v) { if (in_array($v, $select_wechat)) { unset($all_wechat[$k]); } };