新闻中心
Go语言中高效读取大型CSV文件的随机行:水塘抽样法实践

处理大型csv文件时,直接加载全部内容到内存以随机选取行会造成性能瓶颈和内存溢出。本文将介绍如何利用水塘抽样(reservoir sampling)算法,在go语言中以单次遍历、固定内存消耗的方式,从任意大小的csv文件中高效地随机抽取指定数量的行,避免全文件加载,并提供详细的go语言实现示例。
1. 问题背景与挑战
在Go语言中处理CSV文件时,encoding/csv包提供了强大的解析能力。然而,当面对TB级别甚至更大的CSV文件时,常见的csv.NewReader(file).ReadAll()方法会将整个文件内容加载到内存中,这不仅耗时巨大,还极易导致内存溢出(OOM)。特别是当需求是随机抽取文件中的若干行进行分析或测试时,这种全量加载的方式显得非常低效且不切实际。
尽管Go的os.File支持Seek操作以实现文件内的随机访问,但对于CSV文件而言,"随机行"的定义并非简单的字节偏移。不同行的长度可能不一致,这意味着我们无法直接通过计算随机字节偏移来定位到行的起始位置。若要实现行的随机访问,通常需要预先遍历文件构建一个行偏移索引,但这又回到了全量遍历的起点,并且索引本身也可能占用大量内存。
因此,我们需要一种能够在不预知文件总行数、不加载全部内容的情况下,实现单次遍历即可随机抽取指定行数的算法。
2. 水塘抽样(Reservoir Sampling)算法原理
水塘抽样是一种经典算法,用于在不知道数据流总长度的情况下,从数据流中均匀地随机抽取k个样本。它的核心思想是维护一个大小为k的“水塘”(reservoir),并以特定概率替换水塘中的元素。
MedPeer科研绘图
生物医学领域的专业绘图解决方案,告别复杂绘图,专注科研创新
166
查看详情
算法步骤(以选取 k 个样本为例):
- 初始化水塘: 读取数据流的前 k 个元素,将它们放入水塘中。
-
遍历后续元素: 从第 k+1 个元素开始,依次处理数据流中的每一个元素 item_i (假设这是当前处理的第 i 个元素,i 从 0 开始计数)。
- 生成一个 0 到 i 之间的随机整数 j。
- 如果 j 小于 k,则用 item_i 替换水塘中索引为 j 的元素。
- 完成: 当数据流处理完毕后,水塘中剩下的 k 个元素就是从整个数据流中均匀随机抽取的样本。
为什么有效? 该算法保证了数据流中的每个元素被选中并保留在最终水塘中的概率都是 k/N(其中 N 是数据流的总长度)。对于流中的第 i 个元素(i >= k),它被选中的概率是 k/i。一旦被选中,它被后续元素替换的概率会逐渐降低,最终达到均匀分布。
3. Go语言实现:水塘抽样读取CSV随机行
下面我们将使用Go语言实现一个 ReadRandomCSVLines 函数,它利用水塘抽样算法从大型CSV文件中抽取指定数量的随机行。
package main
import (
"encoding/csv"
"fmt"
"io"
"math/rand"
"os"
"time"
)
// ReadRandomCSVLines 使用水塘抽样算法从CSV文件中读取指定数量的随机行。
// 它不会将整个文件加载到内存中,适用于处理大型CSV文件。
// filePath: CSV文件路径
// numSamples: 希望抽取的随机行数
// 返回值: 抽取的随机行([][]string 格式),或错误信息
func ReadRandomCSVLines(filePath string, numSamples int) ([][]string, error) {
if numSamples <= 0 {
return nil, fmt.Errorf("numSamples 必须是正整数")
}
file, err := os.Open(filePath)
if err != nil {
return nil, fmt.Errorf("无法打开文件 %s: %w", filePath, err)
}
defer file.Close()
reader := csv.NewReader(file)
// 如果CSV文件包含标题行,可以在这里读取并跳过它:
// _, err = reader.Read()
// if err != nil && err != io.EOF {
// return nil, fmt.Errorf("无法读取CSV标题行: %w", err)
// }
// 初始化水塘,预分配容量以减少扩容开销
reservoir := make([][]string, 0, numSamples)
var linesRead int64 = 0 // 记录已读取的总行数,使用int64以支持超大文件
// 初始化随
机数生成器,使用当前时间作为种子以确保每次运行结果不同
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for {
record, err := reader.Read() // 读取下一条CSV记录
if err == io.EOF {
break // 文件读取完毕
}
if err != nil {
return nil, fmt.Errorf("读取CSV记录失败: %w", err)
}
linesRead++ // 已读取行数加一
if linesRead <= int64(numSamples) {
// 如果已读取行数小于或等于目标样本数,直接填充水塘
reservoir = append(reservoir, record)
} else {
// 对于后续行,以 numSamples/linesRead 的概率决定是否替换水塘中的一个元素
// 生成一个 [0, linesRead-1] 之间的随机整数
j := r.Int63n(linesRead)
// 如果 j 小于 numSamples,则替换水塘中索引为 j 的元素
if j < int64(numSamples) {
reservoir[j] = record
}
}
}
return reservoir, nil
}
func main() {
// 创建一个虚拟的CSV文件用于测试
dummyData := `id,name,city,age
1,Alice,New York,30
2,Bob,London,24
3,Charlie,Paris,35
4,D*id,Berlin,29
5,Eve,Tokyo,22
6,Frank,Sydney,41
7,Grace,Rome,28
8,Heidi,Madrid,33
9,Ivan,Beijing,27
10,Judy,Moscow,31
11,Kyle,Dubai,26
12,Liam,Toronto,38
13,Mia,Seoul,25
14,Noah,Cairo,32
15,Olivia,Rio,23
16,Peter,Bangkok,36
17,Quinn,Warsaw,21
18,Rachel,Lisbon,29
19,Sam,Dublin,34
20,Tina,Vienna,26
`
filePath := "large_dummy.csv"
err := os.WriteFile(filePath, []byte(dummyData), 0644)
if err != nil {
fmt.Println("创建虚拟文件失败:", err)
return
}
defer os.Remove(filePath) // 程序结束时清理虚拟文件
// 示例1: 抽取 5 条随机行
numSamples1 := 5
fmt.Printf("--- 尝试从 %s 中抽取 %d 条随机行 ---\n", filePath, numSamples1)
randomLines1, err := ReadRandomCSVLines(filePath, numSamples1)
if err != nil {
fmt.Println("读取随机行失败:", err)
return
}
fmt.Println("抽取的随机行 (示例1):")
for i, line := range randomLines1 {
fmt.Printf("%d: %v\n", i+1, line)
}
fmt.Printf("实际抽取行数: %d\n\以上就是Go语言中高效读取大型CSV文件的随机行:水塘抽样法实践的详细内容,更多请关注其它相关文章!
# 不同类型
# 社会化营销推广怎么样
# 童装线上营销推广方案
# 沈河区网站建设服务电话
# 锦州网站优化代理
# 宿迁网站建设啥意思
# 网站建设销售主管招聘
# 分类信息网站 推广
# seo每日
# 外贸工厂的网站建设
# 高明网站优化流程
# 总长度
# 情况下
# 这是
# 检测方法
# 都是
# go
# 会将
# 加载
# 行数
# 遍历
# 为什么
# csv文件
# 性能瓶颈
# unix
# ai
# csv
# 字节
# app
# seo
# go语言
相关栏目:
【
科技资讯46185 】
【
网络学院92790 】
相关推荐:
内存疯狂猛猛涨价:主板销量直接腰斩!
可靠CSGO开箱平台解析 CSGO开箱网合集
怎么在浏览器上运行HTML文件_浏览器运行HTML文件技巧【技巧】
魅族17怎样用浏览器译外语网页_iPhone魅族17浏览器译外语网页【即时翻译】
CSS布局:解决全屏元素100%尺寸与外边距导致的页面溢出问题
AngularJS $http POST请求数据传递与Go后端接收实践
Golang如何测试channel通信行为_Golang channel通信测试与分析方法
Selenium Python中处理点击后新窗口加载冻结问题的策略与实践
Python字典中优雅地迭代剩余元素的方法
Lar*el的路由模型绑定怎么用_Lar*el Route Model Binding简化控制器逻辑
汽水音乐车机版横屏版7.1 汽水音乐车机版横屏版下载入口
如何将HTML表格多行数据保存到Google Sheet
移动端XML文件怎么转换成Excel 手机和平板上的解决方案
J*a TimerTask文件监控:HashMap状态管理与常见陷阱规避指南
铁路12306官网网页端快速入口 铁路12306官方首页登录教程
《明末:渊虚之羽》设计师谈设计角色:那会刚毕业 充满激情
小米Civi 4录制视频过暗_小米Civi 4亮度优化
Win11 BitLocker密码忘了怎么办 Win11找回BitLocker恢复密钥方法【解决】
J*aScript实现单选按钮与关联输入框的联动禁用教程
MinIO大规模对象列表性能瓶颈深度解析与外部元数据管理策略
Promise错误处理:在catch后终止链式then执行的策略
使用CSS更改登录屏幕输入框中PNG图标颜色的策略与局限性
12306选座怎么选到特殊座位_12306特殊座位选择注意事项
uc手机浏览器网页版入口 uc浏览器手机版便捷登录首页
Go语言中高效处理x-www-form-urlencoded表单数据
J*aScript中高效管理与清空动态列表:避免循环陷阱
在Go语言中利用后缀数组处理多字符串:实现高效文本匹配与自动补全
单射、满射与双射的关系 一文理清所有逻辑
拼多多购物车商品数量无法修改如何处理 拼多多购物车操作优化方法
单12V-2×6实现为RTX 5090供电750W!甚至都没敢跑分
在J*a中如何使用BigDecimal进行高精度计算_BigDecimal类应用指南
C++ typeid如何获取类型信息_C++ RTTI运行时类型识别用法
2025年云电脑操作系统体验 | 无需本地硬件,随时随地使用高性能PC
CSS Flexbox如何实现多行排列_flex-wrap wrap自动换行显示
PHP中SSG-WSG API的AES加密实践:正确使用初始化向量
Bilibili动漫最新防封地址发布-Bilibili动漫2025年最稳正版入口推荐
css绝对定位元素脱离父容器怎么办_确保父元素position非static
品牌机怎么重装系统 联想/戴尔/惠普笔记本恢复出厂系统教程
红果短剧网页版官网入口 官方最新网址发布
抖音DOU+怎么投最有效 抖音付费推广的ROI提升技巧
Yandex官网搜索引擎免登录_俄罗斯Yandex一键直达入口
Golang如何处理RPC请求负载均衡_Golang RPC请求负载均衡策略与实践
AO3最新入口2025公告_AO3中文官网合集
如何为你的Composer包编写自动化测试_集成PHPUnit到Composer的scripts工作流
在Pyomo中实现基于变量的条件约束:Big-M方法详解
QQ邮箱登录平台入口 QQ邮箱网页版邮箱官方入口
C++如何实现异步操作_C++11使用std::future和std::async进行异步编程
PHP表单数据传递:如何通过隐藏输入字段获取动态ID
文心一言怎样用插件调度API数据_文心一言用插件调度API数据【API调用】
利用5118提升短视频内容效果_5118短视频关键词优化方法


2025-12-04
浏览次数:次
返回列表
机数生成器,使用当前时间作为种子以确保每次运行结果不同
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for {
record, err := reader.Read() // 读取下一条CSV记录
if err == io.EOF {
break // 文件读取完毕
}
if err != nil {
return nil, fmt.Errorf("读取CSV记录失败: %w", err)
}
linesRead++ // 已读取行数加一
if linesRead <= int64(numSamples) {
// 如果已读取行数小于或等于目标样本数,直接填充水塘
reservoir = append(reservoir, record)
} else {
// 对于后续行,以 numSamples/linesRead 的概率决定是否替换水塘中的一个元素
// 生成一个 [0, linesRead-1] 之间的随机整数
j := r.Int63n(linesRead)
// 如果 j 小于 numSamples,则替换水塘中索引为 j 的元素
if j < int64(numSamples) {
reservoir[j] = record
}
}
}
return reservoir, nil
}
func main() {
// 创建一个虚拟的CSV文件用于测试
dummyData := `id,name,city,age
1,Alice,New York,30
2,Bob,London,24
3,Charlie,Paris,35
4,D*id,Berlin,29
5,Eve,Tokyo,22
6,Frank,Sydney,41
7,Grace,Rome,28
8,Heidi,Madrid,33
9,Ivan,Beijing,27
10,Judy,Moscow,31
11,Kyle,Dubai,26
12,Liam,Toronto,38
13,Mia,Seoul,25
14,Noah,Cairo,32
15,Olivia,Rio,23
16,Peter,Bangkok,36
17,Quinn,Warsaw,21
18,Rachel,Lisbon,29
19,Sam,Dublin,34
20,Tina,Vienna,26
`
filePath := "large_dummy.csv"
err := os.WriteFile(filePath, []byte(dummyData), 0644)
if err != nil {
fmt.Println("创建虚拟文件失败:", err)
return
}
defer os.Remove(filePath) // 程序结束时清理虚拟文件
// 示例1: 抽取 5 条随机行
numSamples1 := 5
fmt.Printf("--- 尝试从 %s 中抽取 %d 条随机行 ---\n", filePath, numSamples1)
randomLines1, err := ReadRandomCSVLines(filePath, numSamples1)
if err != nil {
fmt.Println("读取随机行失败:", err)
return
}
fmt.Println("抽取的随机行 (示例1):")
for i, line := range randomLines1 {
fmt.Printf("%d: %v\n", i+1, line)
}
fmt.Printf("实际抽取行数: %d\n\