40GB大CSV文件随机采样1万行的提速方案咨询
你的sed方案可行但效率不高,核心原因是:sed处理多个行号匹配时,每一行都要线性扫描你提供的1万个行号列表,180万行的遍历加上反复的比对操作,自然会耗费大量时间。下面给你几个更高效的替代方案,从简单工具到算法级优化都有:
1. 用shuf工具(最简单直接)
如果你的系统是Linux/macOS(macOS需要先通过brew安装coreutils,即brew install coreutils,然后用gshuf),shuf是专门为随机打乱/采样设计的工具,底层做了大量优化,处理大文件效率很高:
# Linux直接用 shuf -n 10000 input.csv > output.csv # macOS需要用gshuf gshuf -n 10000 input.csv > output.csv
shuf会自动处理大文件的内存占用问题,不需要你预先生成行号,一行命令搞定,速度比sed快很多。
2. Awk哈希表匹配(预生成行号的优化版)
如果你还是想保留“预生成随机行号”的思路,可以用Awk的哈希表替代sed的线性匹配,把行号查找的时间复杂度从O(K)降到O(1)(K是采样数):
步骤1:生成无重复的随机行号(保存到临时文件)
用shuf生成1万个1到1800000之间的不重复行号:
shuf -i 1-1800000 -n 10000 > random_lines.txt
步骤2:用Awk加载行号哈希表并采样
创建一个sample.awk脚本:
BEGIN { # 把随机行号加载到哈希表 while ((getline line < "random_lines.txt") > 0) { target_lines[line] = 1 } close("random_lines.txt") } # 当前行号在哈希表中就输出 NR in target_lines { print }
运行脚本:
awk -f sample.awk input.csv > output.csv
这个方案比sed快数倍,因为Awk的哈希表查找是瞬时的,不会像sed那样逐行比对所有行号。
3. 蓄水池抽样算法(最优大文件方案)
如果你的文件大到连预生成行号都觉得麻烦,或者不确定总行数,蓄水池抽样算法是最优选择——它只需要遍历一次文件,内存仅需存储1万行样本,完全不需要预先知道总行数:
创建reservoir_sample.awk脚本:
BEGIN { # 初始化随机种子 srand() # 采样数量 sample_size = 10000 } # 前sample_size行直接存入蓄水池 NR <= sample_size { reservoir[NR] = $0 next } # 从第sample_size+1行开始,随机替换蓄水池中的行 { # 生成1到当前行号的随机数 random_pos = int(rand() * NR) + 1 if (random_pos <= sample_size) { reservoir[random_pos] = $0 } } # 遍历结束后输出蓄水池中的所有样本 END { for (i in reservoir) { print reservoir[i] } }
运行脚本:
awk -f reservoir_sample.awk input.csv > output.csv
这个方法的时间复杂度是O(N)(N是总行数),内存占用仅为1万行数据的大小,对于40GB的大文件来说,是效率最高的方案之一。
额外提示:处理CSV表头
如果你的CSV有表头,需要保留表头并采样1万行数据,可以稍微修改脚本:先保存第一行,然后从第二行开始抽样9999行,最后把表头和抽样数据合并。以蓄水池抽样为例:
BEGIN { srand() sample_size = 9999 } NR == 1 { # 保存表头 header = $0 next } NR <= sample_size + 1 { reservoir[NR-1] = $0 next } { random_pos = int(rand() * (NR-1)) + 1 if (random_pos <= sample_size) { reservoir[random_pos] = $0 } } END { print header for (i in reservoir) { print reservoir[i] } }
内容的提问来源于stack exchange,提问作者MrD

