新闻中心
Go语言中从大型CSV文件随机读取行的策略:蓄水池抽样详解

在go语言中处理大型csv文件时,直接加载全部内容进行随机行抽取会导致严重的内存和性能问题。本文将介绍一种高效的解决方案——蓄水池抽样算法(reservoir sampling),它允许在单次遍历文件的情况下,以恒定的内存开销随机选择指定数量的行,从而避免了将整个文件加载到内存中,特别适用于大数据场景下的数据采样和测试。
大文件随机行抽取面临的挑战
当我们需要从一个非常大的文本文件(如CSV文件)中随机选择几行进行处理或测试时,一个常见的直观做法是使用 encoding/csv 包的 ReadAll() 方法将整个文件内容加载到内存中,然后从内存中的切片随机选取。例如:
reader := csv.NewReader(file) lines, err := reader.ReadAll() // 潜在的内存和性能瓶颈 // 然后从 lines 中随机选择
这种方法对于小文件是可行的,但对于GB甚至TB级别的大文件,它会迅速耗尽系统内存,并导致漫长的文件读取时间。io.Reader 接口本身是流式的,不直接支持随机跳转(seek)到文件中的任意“行”位置,因为行的长度不固定。因此,我们需要一种在不完全加载文件的情况下,依然能够保证随机性的方法。
蓄水池抽样算法原理
解决上述问题的核心是蓄水池抽样(Reservoir Sampling)算法。它是一种在线算法,可以在不知道数据流总长度的情况下,从数据流中等概率地随机选择 k 个样本。其主要优点是内存使用量恒定,只与要抽取的样本数量 k 相关,而与数据流的总长度无关。
算法步骤如下:
MedPeer科研绘图
生物医学领域的专业绘图解决方案,告别复杂绘图,专注科研创新
166
查看详情
- 初始化蓄水池: 创建一个大小为 k 的容器(蓄水池),用于存放最终选定的 k 个样本。
- 填充蓄水池: 从数据流中读取前 k 个元素,并将其直接放入蓄水池中。
- 遍历后续元素: 从第 k+1 个元素开始,依次处理数据流中的每一个元素(假设当前处理的是第 i 个元素,其中 i 从 k 开始计数,即 i 是当前已读取元素的总数减一)。 a. 生成一个 0 到 i 之间(包含 0 和 i)的随机整数 j。 b. 如果 j 小于 k,则将当前元素替换蓄水池中的第 j 个元素。 c. 如果 j 大于或等于 k,则当前元素被丢弃,蓄水池保持不变。
通过这种方式,算法保证了数据流中每一个元素被选入蓄水池的概率都是 k/N(其中 N 是数据流的总长度),并且在任何时候,蓄水池中的 k 个元素都是从已处理过的所有元素中随机选取的。
Go语言实现:使用 encoding/csv 进行蓄水池抽样
以下是使用Go语言 encoding/csv 包结合蓄水池抽
样算法从大型CSV文件中随机抽取指定数量记录的示例代码:
package main
import (
"encoding/csv"
"fmt"
"io"
"log"
"math/rand"
"os"
"strconv"
"time"
)
// ReadRandomCSVRecordsReservoir 从指定CSV文件中使用蓄水池抽样算法读取 k 个随机记录。
// 返回一个包含 k 个 [][]string 类型的记录切片。
func ReadRandomCSVRecordsReservoir(filePath string, k int) ([][]string, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, fmt.Errorf("打开文件失败: %w", err)
}
defer file.Close()
reader := csv.NewReader(file)
// reservoir 用于存放随机抽取的 k 个记录。
// 预分配容量以避免多次扩容。
reservoir := make([][]string, 0, k)
// 使用当前时间戳作为种子初始化随机数生成器,确保每次运行结果不同。
r := rand.New(rand.NewSource(time.Now().UnixNano()))
recordCount := 0 // 记录当前已处理的CSV记录总数
for {
record, err := reader.Read() // 读取单个CSV记录
if err == io.EOF {
break // 文件读取完毕
}
if err != nil {
return nil, fmt.Errorf("读取CSV记录错误: %w", err)
}
recordCount++ // 增加已处理记录计数
if recordCount <= k {
// 前 k 个记录直接填充蓄水池
reservoir = append(reservoir, record)
} else {
// 对于第 k+1 个及之后的记录
// 生成一个 [0, recordCount-1] 之间的随机整数 j
j := r.Intn(recordCount)
// 如果 j 小于 k,则用当前记录替换蓄水池中的第 j 个记录
if j < k {
reservoir[j] = record
}
}
}
// 如果文件中的总记录数少于 k,则返回所有记录
if recordCount < k {
return reservoir, nil
}
return reservoir, nil
}
func main() {
// 创建一个大型虚拟CSV文件用于测试
dummyFileName := "large_data.csv"
createDummyCSVFile(dummyFileName, 1000000) // 创建包含100万行的文件
k := 10 // 需要抽取的随机记录数量
fmt.Printf("正在从 %s 中抽取 %d 个随机CSV记录...\n", dummyFileName, k)
selectedRecords, err := ReadRandomCSVRecordsReservoir(dummyFileName, k)
if err != nil {
log.Fatalf("抽取随机CSV记录失败: %v", err)
}
fmt.Println("抽取的记录:")
for i, record := range selectedRecords {
fmt.Printf("%d: %v\n", i+1, record)
}
// 清理虚拟文件
os.Remove(dummyFileName)
}
// createDummyCSVFile 是一个辅助函数,用于生成一个指定行数的虚拟CSV文件。
func createDummyCSVFile(filename string, numLines int) {
file, err := os.Create(filename)
if err != nil {
log.Fatalf("创建虚拟CSV文件失败: %v", err)
}
defer file.Close()
writer := csv.NewWriter(file)
defer writer.Flush() // 确保所有缓冲数据写入文件
// 写入CSV头部
writer.Write([]string{"ID", "Name", "Description", "Value"})
for i := 0; i < numLines; i++ {
record := []string{
strconv.Itoa(i + 1),
fmt.Sprintf("Item_%d", i+1),
// 包含逗号和换行的描述,以测试CSV解析器的鲁棒性
fmt.Sprintf("这是第 %d 项的描述,其中可能包含逗号, 甚至换行符。\n描述的第二行。", i+1),
fmt.Sprintf("%.2f", float64(i)*1.23),
}
err := writer.Write(record)
if err != nil {
log.Fatalf("写入虚拟CSV记录失败: %v", err)
}
}
fmt.Printf("已创建虚拟CSV文件 %s,包含 %d 条记录 (含头部).\n", filename, numLines+1)
}注意事项与总结
- 随机数种子: 在示例代码中,我们使用 time.Now().UnixNano() 作为 rand.NewSource() 的种子。这确保了每次程序运行时,抽样结果通常是不同的。在需要可重现结果的测试场景中,可以使用固定的种子值。
- 内存效率: 该方法的核心优势在于其恒定的内存占用。无论CSV文件有多大,内存中只存储 k 个记录,而不是整个文件。
- 性能: 尽管内存占用低,但该方法仍然需要遍历整个文件。因此,文件读取时间会随着文件大小的增加而增加。然而,这通常比将整个文件加载到内存中再进行处理要快,因为它避免了大量的内存分配和垃圾回收开销。
- CSV记录与物理行: encoding/csv 包能够正确处理包含逗号、引号或换行符的CSV字段。这意味着 reader.Read() 返回的一个 []string 记录可能对应于文件中的多行物理文本。蓄水池抽样算法基于 csv.Reader 返回的逻辑记录进行操作,因此是可靠的。
- 抽样数量 k: k 的值应根据实际需求设定。如果 k 过大,接近文件总行数,则内存优势会减弱。
- 错误处理: 在实际应用中,对文件操作和CSV读取过程中可能出现的错误进行健壮的处理至关重要。
通过采用蓄水池抽样算法,Go语言开发者可以高效地从大型CSV文件中随机抽取数据,而无需担心内存溢出问题,从而在处理大数据集时保持应用程序的稳定性和性能。
以上就是Go语言中从大型CSV文件随机读取行的策略:蓄水池抽样详解的详细内容,更多请关注其它相关文章!
# 总长度
# 商务网站推广步骤
# 淘宝 后台seo在哪里
# 重庆市织梦网站建设
# 淘宝女性购买关键词排名
# 北京营销推广机构
# 江门慧抖销seo优化
# 密云网站建设代办
# 企业seo优化教程
# 郴州英文网站建设
# 网站建设公司选择方法
# 都是
# 的是
# 不同类型
# 创建一个
# go
# 情况下
# 随机数
# 池中
# 遍历
# 加载
# 内存占用
# csv文件
# 性能瓶颈
# unix
# ai
# csv
# app
# 大数据
# go语言
相关栏目:
【
科技资讯46185 】
【
网络学院92790 】
相关推荐:
Mac终端命令大全_Mac常用Terminal指令速查
vivo云服务网页版登录 怎么登录vivo云服务网页版
蛙漫移动版在线看 蛙漫手机浏览器直达入口
Win11 BitLocker密码忘了怎么办 Win11找回BitLocker恢复密钥方法【解决】
优化LangChain文档加载与ChromaDB集成:解决多文档处理与分块问题
J*a中实现Go语言select通道多路复用机制
AI抖音网页版免费视频入口 AI抖音网页端最新视频实时观看
LINUX怎么设置定时任务_LINUX crontab配置教程
天眼查怎么看公司融资情况 天眼查企业融资历史查询步骤【攻略】
蛙漫漫画官网在线入口 蛙漫全本漫画免费阅读平台
4399免费游戏网址入口 4399小游戏免费入口点开即玩
在Go Martini框架中高效服务动态生成图像的实践指南
动漫共和国防屏蔽稳定域名-动漫共和国官方正版直达通道
网易大神账号申诉需要多久_网易大神账号申诉流程说明
在Blazor WebAssembly应用中动态注入客户端特定指标代码的策略
电脑安装程序提示“错误1722”怎么办_Windows Installer服务问题解决【教程】
《刺客信条:影》PS5 Pro和Switch 2画面对比
Yandex官网免登录入口_俄罗斯Yandex搜索引擎一键访问
如何在 Excel Online 和 Google 表格中更改日期格式
J*a实现学校排课程序_面向对象结构化项目示例
msn官网入口地址手机版 msn官方网站手机最新链接
妖精漫画网页版登录入口免费_妖精漫画官网主页直接阅读漫画
mc.js免安装版 mc.js一键畅玩入口
护手霜蹭到袖口上了如何清洗? 怎样避免留下一圈油印?
CSS Grid如何控制元素对齐_align-items与justify-items组合使用
J*aScript对象创建方式_J*aScript设计模式应用
C++如何解决segmentation fault_C++段错误调试与原因分析
双系统安装时,如何设置默认启动系统? msconfig命令了解一下!
蛙漫2台版漫画地址 Manwa2正版网页版链接
Mac怎么使用表情符号_Mac Emoji快捷键面板
美团外卖商家服务中心入口 美团商家版官网入口
在哪找SublimeJ远程工具_SFTP插件配置教程
拼多多视频播放卡顿如何处理 拼多多视频播放优化技巧
抖音极速版最新版本 抖音极速版官方下载地址
Basecamp怎样用留言钉固定重点_Basecamp用留言钉固定重点【重点标记】
TikTok国际版官网直达_TikTok国际版官网直达进入在线观看
荣耀Play7TPro怎样在信息App置顶客服对话_iPhone荣耀Play7TPro信息App置顶客服对话【优先查看】
Win11怎么安装Linux子系统 Win11 WSL2安装Ubuntu及环境配置指南
mc.js游戏直达 mc.js网页免下载版本秒进地址
优化 Python 函数中的条件逻辑:解决 if-else 嵌套与参数选择问题
印象笔记如何设提醒任务防漏执行_印象笔记设提醒任务防漏执行【任务提醒】
Win11怎么查看电脑配置_Win11硬件配置检测工具使用
解决移动端滚动问题的overflow属性应用指南
Win11怎么用U盘重装系统 Win11制作启动盘并重装系统完整教程【详解】
正确连接J*aScript到HTML实现可点击图片与自定义事件处理
sublime怎么进行远程开发编辑_配置rsub/rmate实现sublime编辑服务器文件
解决Tabulator日期时间排序问题的专业指南
修复二维数组索引越界异常:一维循环到二维坐标的正确映射
QQ邮箱网页版登录入口 QQ邮箱官方在线使用平台
Lar*el Form Request中唯一性验证在更新操作中的正确实现


2025-12-04
浏览次数:次
返回列表