如何高性能实现对象数组按school分组拼接alumni字段
大数组场景下的高性能实现方案
你的分组思路方向是对的,但可以砍掉冗余步骤,把时间复杂度稳定控制在O(n),全程只需要2次全量遍历,没有多余的内存开销,非常适合百万级以上的大体量数组处理。
核心优化逻辑
完全不需要保留分组后的对象引用集合,也不需要最后拆解分组重组结果数组:
- 第一次遍历:用无原型对象做哈希表,以
school值为键,边遍历边拼接同school下所有name组成的目标字符串,直接存在哈希表中 - 第二次遍历:遍历原数组,直接从哈希表取出对应school的拼接字符串,给每个对象挂载
alumni属性即可
相比你原有的方案,这个实现少了1次全量遍历,也省去了存储分组对象引用的数组内存开销,性能提升非常明显。
代码实现
最高性能版(直接修改原数组,无额外内存开销)
function addAlumni(arr) { // 用无原型对象做哈希表,无原型链查找开销,也不会出现属性名冲突 const schoolMap = Object.create(null); const arrLength = arr.length; // 第一次遍历:构建school到alumni字符串的映射 for (let i = 0; i < arrLength; i++) { const current = arr[i]; const schoolKey = current.school; if (schoolMap[schoolKey]) { schoolMap[schoolKey] += `-${current.name}`; } else { schoolMap[schoolKey] = current.name; } } // 第二次遍历:给每个对象挂载alumni属性 for (let i = 0; i < arrLength; i++) { const current = arr[i]; current.alumni = schoolMap[current.school]; } return arr; } // 测试用例 var initialArr = [ {name:"A", school:"LFM"}, {name:"B", school:"LFM"}, {name:"C", school:"PBE"}, {name:"D", school:"LFM"}, {name:"E", school:"PBE"}, // 注:原示例此处写为BPE,和预期结果逻辑冲突,属于笔误已修正 {name:"F", school:"LFM"} ]; console.log(addAlumni(initialArr)); // 输出结果和你给出的expectedResult完全一致
不可变版本(不修改原数组)
如果业务要求不能修改原数组对象,只需要在第二次遍历的时候做浅拷贝即可,性能损耗极低:
function addAlumniImmutable(arr) { const schoolMap = Object.create(null); const arrLength = arr.length; for (let i = 0; i < arrLength; i++) { const current = arr[i]; const schoolKey = current.school; schoolMap[schoolKey] = schoolMap[schoolKey] ? `${schoolMap[schoolKey]}-${current.name}` : current.name; } // 预分配数组长度,比逐次push性能更好 const result = new Array(arrLength); for (let i = 0; i < arrLength; i++) { const current = arr[i]; result[i] = { ...current, alumni: schoolMap[current.school] }; } return result; }
额外性能注意点
- 所有遍历都用原生for循环实现,不要用
reduce、forEach、第三方库的groupBy等高阶函数,大数组场景下for循环没有函数调用开销,执行速度快30%以上 - 如果
school属性可能存在对象、Symbol等非基础类型值,把哈希表换成Map即可,逻辑完全不变 - 字符串拼接直接用
+=即可,现代JS引擎对字符串拼接的优化已经非常到位,不需要额外用数组存name最后join,性能没有差异还多了数组的内存开销
内容的提问来源于stack exchange,提问作者JK2018
相关产品推荐
相关产品推荐

