求助:使用JavaScript对2800万条字符串去重的可行方案
大体积JSON字符串去重与统计解决方案
JavaScript(Node.js)可行方案
流式读取+Set结构
针对大文件无法一次性加载的问题,采用流式解析结合Set(比普通对象更高效的唯一值存储结构),避免内存溢出:
场景1:JSON为每行一条字符串格式
const fs = require('fs'); const readline = require('readline'); const uniqueSet = new Set(); const outputStream = fs.createWriteStream('unique_output.json'); outputStream.write('['); let isFirst = true; // 创建逐行读取接口 const rl = readline.createInterface({ input: fs.createReadStream('input.json'), crlfDelay: Infinity }); rl.on('line', (line) => { // 清理行内的引号、逗号等格式符号 const str = line.trim().replace(/^"|"|,$/g, ''); if (!uniqueSet.has(str)) { uniqueSet.add(str); // 写入输出文件,处理逗号分隔 if (!isFirst) outputStream.write(','); outputStream.write(`"${str}"`); isFirst = false; } }); rl.on('close', () => { outputStream.write(']'); outputStream.end(); console.log(`唯一字符串数量:${uniqueSet.size}`); });
场景2:JSON为大数组格式(如["a","b",...])
使用JSONStream库流式解析数组元素,避免一次性加载整个数组:
const fs = require('fs'); const JSONStream = require('JSONStream'); const uniqueSet = new Set(); const outputStream = fs.createWriteStream('unique_output.json'); outputStream.write('['); let isFirst = true; fs.createReadStream('input.json') .pipe(JSONStream.parse('*')) // 解析数组中的每个元素 .on('data', (str) => { if (!uniqueSet.has(str)) { uniqueSet.add(str); if (!isFirst) outputStream.write(','); outputStream.write(JSON.stringify(str)); isFirst = false; } }) .on('end', () => { outputStream.write(']'); outputStream.end(); console.log(`唯一字符串数量:${uniqueSet.size}`); });
安装依赖:npm install jsonstream
注意:若预估2.4亿条唯一值超出内存(单条100字符约占24GB内存),JavaScript方案会受限,需转向磁盘分桶或其他语言方案。
其他语言高效方案
Python 哈希分桶方案(内存不足时首选)
当内存无法容纳所有唯一值时,通过哈希分桶将数据拆分到多个临时文件,再逐个去重合并,分摊内存压力:
import json import hashlib import os import ijson # 分桶数量,根据内存调整(示例设为100) BUCKET_NUM = 100 temp_files = {} # 第一步:流式读取并分桶 for i in range(BUCKET_NUM): temp_files[i] = open(f"temp_{i}.txt", "w", encoding="utf-8") with open("input.json", "r", encoding="utf-8") as f: # 流式解析JSON数组中的每个元素 for str_item in ijson.items(f, "item"): # 计算哈希值取模,分配到对应桶 hash_digest = hashlib.md5(str_item.encode()).hexdigest() bucket_idx = int(hash_digest, 16) % BUCKET_NUM temp_files[bucket_idx].write(f"{str_item}\n") # 关闭临时文件 for f in temp_files.values(): f.close() # 第二步:逐个桶去重并合并结果 unique_count = 0 with open("unique_output.json", "w", encoding="utf-8") as out_f: out_f.write("[") is_first = True for i in range(BUCKET_NUM): temp_path = f"temp_{i}.txt" if not os.path.exists(temp_path): continue unique_in_bucket = set() with open(temp_path, "r", encoding="utf-8") as temp_f: for line in temp_f: s = line.strip() if s not in unique_in_bucket: unique_in_bucket.add(s) if not is_first: out_f.write(",") out_f.write(json.dumps(s)) is_first = False unique_count += 1 os.remove(temp_path) out_f.write("]") print(f"唯一字符串数量:{unique_count}")
安装依赖:pip install ijson
Go 高性能流式方案(内存充足时首选)
Go的内存效率和并发特性适合处理超大规模数据,用map[string]struct{}作为轻量去重结构:
package main import ( "bufio" "encoding/json" "fmt" "os" "strings" ) func main() { inputFile, err := os.Open("input.json") if err != nil { panic(err) } defer inputFile.Close() outputFile, err := os.Create("unique_output.json") if err != nil { panic(err) } defer outputFile.Close() writer := bufio.NewWriter(outputFile) writer.WriteString("[") defer writer.WriteString("]") defer writer.Flush() uniqueMap := make(map[string]struct{}) scanner := bufio.NewScanner(inputFile) isFirst := true // 跳过JSON数组开头的[ if scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if line != "[" { panic("输入JSON不是标准数组格式") } } for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) // 清理行尾的逗号或] line = strings.TrimSuffix(line, ",") line = strings.TrimSuffix(line, "]") if line == "" { continue } var str string if err := json.Unmarshal([]byte(line), &str); err != nil { continue } if _, exists := uniqueMap[str]; !exists { uniqueMap[str] = struct{}{} if !isFirst { writer.WriteString(",") } jsonStr, _ := json.Marshal(str) writer.Write(jsonStr) isFirst = false } } if err := scanner.Err(); err != nil { panic(err) } fmt.Printf("唯一字符串数量:%d\n", len(uniqueMap)) }
方案选择建议
- 内存充足(32GB+):优先选Go方案,其次是Node.js流式+Set方案,性能最优。
- 内存不足(<32GB):选Python哈希分桶方案,通过磁盘分摊内存压力。
内容的提问来源于stack exchange,提问作者Kirk Ouimet
相关产品推荐
相关产品推荐

