1. 数组去重的核心场景与价值
数组去重是每个开发者都会遇到的基础问题,但不同场景下的处理策略差异巨大。我在处理电商SKU列表时曾遇到一个典型案例:前端需要展示2000+商品去重后的分类标签,但直接使用Set导致页面卡顿3秒。这个经历让我意识到,看似简单的去重操作背后藏着不少门道。
从内存占用角度看,一个包含100万个整数的数组在64位系统中占用约8MB内存,而去重后的数组可能只占几百KB。但去重过程本身会产生临时内存开销,这在移动端或嵌入式系统中尤为关键。去年我们团队在智能家居项目中就因数组去重算法选择不当,导致树莓派设备频繁OOM崩溃。
业务场景的多样性也决定了没有放之四海皆皆准的方案:
- 前端展示层通常关注即时响应,适合用空间换时间
- 后端批量处理更在意内存效率,可能需要流式处理
- 数据库去重则要考虑索引利用和磁盘IO成本
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 基础实现方案对比
2.1 经典双循环法
最朴素的实现是嵌套循环比对,虽然时间复杂度O(n²),但在小数据量(<1000)时反而可能最快。我在处理Chrome插件的小型配置数组时,实测发现双循环比Set快15%:
javascript复制function unique(arr) {
const res = []
for (let i = 0; i < arr.length; i++) {
let isDuplicate = false
for (let j = 0; j < res.length; j++) {
if (arr[i] === res[j]) {
isDuplicate = true
break
}
}
if (!isDuplicate) res.push(arr[i])
}
return res
}
注意:此方法在数组元素为对象时会失效,因为对象比较的是引用地址
2.2 哈希表优化方案
利用Set或Object的哈希特性可以将时间复杂度降到O(n),这是最常用的生产环境方案。但实际使用时要注意:
-
数字和字符串会被自动转换类型:
javascript复制const arr = [1, '1', 2] [...new Set(arr)] // 输出[1, '1', 2] -
对象去重需要序列化处理:
javascript复制const objArr = [{a:1}, {a:1}] const uniqueObj = [...new Set(objArr.map(JSON.stringify))].map(JSON.parse)
2.3 排序预处理法
先排序再去重的时间复杂度是O(nlogn),适合需要保持原数组顺序的场景。在Node.js处理大型日志文件时,这个方法比Set更节省内存:
javascript复制function uniqueSorted(arr) {
arr.sort()
return arr.filter((item, index) => index === 0 || item !== arr[index-1])
}
3. 特殊数据类型处理
3.1 对象数组去重
处理对象数组时,常规方法会失效。我在电商项目中遇到过商品SKU去重问题,最终采用组合键的方案:
javascript复制function uniqueByKey(arr, key) {
const seen = new Set()
return arr.filter(item => {
const compositeKey = JSON.stringify(
Array.isArray(key)
? key.map(k => item[k])
: item[key]
)
return seen.has(compositeKey) ? false : seen.add(compositeKey)
})
}
// 使用示例
const products = [
{id:1, sku:'A1'},
{id:2, sku:'A1'},
{id:1, sku:'B2'}
]
uniqueByKey(products, 'id') // 按id去重
uniqueByKey(products, ['id', 'sku']) // 组合键去重
3.2 多维数组处理
处理二维数组时,需要递归或flat操作。我在可视化项目中处理地理坐标数据时,发明了这种混合方案:
javascript复制function deepUnique(arr) {
const primitives = new Set()
const objects = new Map()
return arr.filter(item => {
if (typeof item !== 'object' || item === null) {
return !primitives.has(item) && primitives.add(item)
}
const key = JSON.stringify(item)
return !objects.has(key) && objects.set(key, true)
})
}
4. 性能优化实战
4.1 大数据量分块处理
当数组超过10万条时,内存可能成为瓶颈。我在分析用户行为日志时,采用分块处理策略:
javascript复制async function hugeArrayUnique(arr, chunkSize = 50000) {
const result = []
for (let i = 0; i < arr.length; i += chunkSize) {
const chunk = arr.slice(i, i + chunkSize)
result.push(...[...new Set(chunk)])
await new Promise(resolve => setTimeout(resolve, 0)) // 避免阻塞
}
return [...new Set(result)] // 最终去重
}
4.2 类型化数组优化
对于纯数字数组,使用TypedArray可以大幅提升性能。在WebGL项目中,这个方法使去重速度提升8倍:
javascript复制function numericUnique(arr) {
const buffer = new Uint32Array(arr)
const unique = new Uint32Array(new Set(buffer))
return Array.from(unique)
}
5. 各语言实现对比
5.1 Java实现方案
Java 8+推荐使用Stream API,但要注意并行流的线程安全问题:
java复制// 基础类型去重
int[] arr = {1,2,2,3};
int[] unique = Arrays.stream(arr).distinct().toArray();
// 对象去重
List<Person> people = ...;
List<Person> uniquePeople = people.stream()
.collect(Collectors.toMap(
Person::getId,
Function.identity(),
(existing, replacement) -> existing
))
.values()
.stream()
.collect(Collectors.toList());
5.2 Python特色实现
Python的列表推导式配合字典保持顺序:
python复制from typing import List, TypeVar
T = TypeVar('T')
def unique_keep_order(arr: List[T]) -> List[T]:
return list(dict.fromkeys(arr))
# 对象列表去重
def unique_by_key(arr: List[dict], key: str) -> List[dict]:
seen = set()
return [x for x in arr if not (x[key] in seen or seen.add(x[key]))]
5.3 C++高效方案
对于性能敏感场景,C++可以使用STL算法:
cpp复制#include <algorithm>
#include <vector>
template<typename T>
std::vector<T> unique_vector(std::vector<T> vec) {
std::sort(vec.begin(), vec.end());
vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
return vec;
}
// 自定义对象去重
struct Person {
int id;
bool operator==(const Person& other) const {
return id == other.id;
}
};
namespace std {
template<> struct hash<Person> {
size_t operator()(const Person& p) const {
return hash<int>()(p.id);
}
};
}
std::vector<Person> unique_persons(std::vector<Person> people) {
std::unordered_set<Person> s(people.begin(), people.end());
return std::vector<Person>(s.begin(), s.end());
}
6. 生产环境中的陷阱
6.1 浮点数精度问题
处理财务数据时遇到的经典问题:
javascript复制const floats = [0.1, 0.2, 0.1 + 0.2]
console.log([...new Set(floats)]) // 输出[0.1, 0.2, 0.30000000000000004]
// 解决方案
function floatUnique(arr, precision = 12) {
const seen = new Set()
return arr.filter(n => {
const key = Math.round(n * 10**precision)
return seen.has(key) ? false : seen.add(key)
})
}
6.2 特殊值处理
NaN、undefined等特殊值的处理差异:
javascript复制const tricky = [NaN, undefined, null, NaN, undefined]
console.log([...new Set(tricky)])
// 输出[NaN, undefined, null] ✅
// 但注意:
console.log(NaN === NaN) // false
// Set能识别NaN是特例
6.3 内存泄漏风险
长期运行的Node服务中,不当的缓存会导致内存泄漏:
javascript复制// 反例:全局缓存
const globalCache = new Set()
function processRequest(items) {
items.forEach(item => globalCache.add(item))
return [...globalCache]
}
// 正解:WeakMap + 定时清理
const weakCache = new WeakMap()
setInterval(() => {
if (weakCache.size > 10000) weakCache.clear()
}, 3600000)
7. 高级应用场景
7.1 流式去重处理
处理超大数据集时(如日志分析),需要流式处理。我在ELK系统中实现的方案:
javascript复制const { Transform } = require('stream')
class UniqueStream extends Transform {
constructor(options) {
super({...options, objectMode: true})
this.seen = new Set()
}
_transform(chunk, encoding, callback) {
const key = JSON.stringify(chunk)
if (!this.seen.has(key)) {
this.seen.add(key)
this.push(chunk)
}
callback()
}
}
// 使用示例
fs.createReadStream('huge.log')
.pipe(csvParser())
.pipe(new UniqueStream())
.pipe(fs.createWriteStream('unique.log'))
7.2 分布式去重
在MapReduce框架中实现去重逻辑:
java复制// Mapper
public class UniqueMapper extends Mapper<LongWritable, Text, Text, NullWritable> {
private Text outKey = new Text();
protected void map(LongWritable key, Text value, Context context) {
outKey.set(value.toString().trim());
context.write(outKey, NullWritable.get());
}
}
// Reducer
public class UniqueReducer extends Reducer<Text, NullWritable, Text, NullWritable> {
protected void reduce(Text key, Iterable<NullWritable> values, Context context) {
context.write(key, NullWritable.get());
}
}
7.3 前端虚拟列表优化
处理大型列表渲染时,结合去重与虚拟滚动:
javascript复制import { useMemo } from 'react'
function useUniqueVirtualList(data, { keyFn, overscan = 10 }) {
const uniqueData = useMemo(() => {
const seen = new Set()
return data.filter(item => {
const key = keyFn(item)
return seen.has(key) ? false : seen.add(key)
})
}, [data, keyFn])
// 虚拟滚动逻辑...
return { uniqueData, virtualItems }
}
