Go Excelize 怎么流式写入大量工作表行
我在做 XLSX 导出时,最先遇到的不是 Excelize API 难,而是“把所有行先装进一个大切片”这件事会让内存随着数据量一起涨。Go Excelize 针对这种场景提供了 StreamWriter:先拿到流式写入器,再按递增行号写入每一行,最后调用 Flush,接着保存文件。
这篇只解决一个问题:Go Excelize 怎么流式写入大量工作表行。示例使用官方当前仓库与流式写入文档中的 API 语义,不把流式写入包装成 Excelize 的全部功能介绍。
官方地址:https://github.com/qax-os/excelize
我为什么从一次性写入换成 StreamWriter
一次性写入的代码看起来很直观:先构造包含全部记录的二维切片,再交给工作表 API。数据只有几千行时,这种方式没有明显压力;但当导出任务达到几十万行,数据对象、切片容量和 XLSX 组装过程会同时占用内存,导出任务就容易和业务请求争抢资源。
Excelize 的流式方案把关注点收窄到“当前要写的行”。官方文档说明,NewStreamWriter 用于向已经存在的空白工作表写入大量数据;当内存中的数据块超过 16MB 时,流式写入器会尝试使用临时文件来降低内存占用。这里的关键不是保证内存永远固定,而是避免把所有待写行长期保留在应用自己的大切片里。
我的判断是:导出数据量稳定且很小,普通 API 更省心;导出行数明显增长、又不需要在同一张表里边写边读时,StreamWriter 才值得引入。

用 NewStreamWriter 按递增行号写入行
最小实现可以分成四个对象关系:创建工作簿,获取现有空白工作表的流式写入器,按行号调用 SetRow,最后结束流。行坐标可以用 CoordinatesToCellName 从列号和行号生成,避免手工拼接大量坐标字符串。
package main
import (
"fmt"
"log"
"github.com/xuri/excelize/v2"
)
func main() {
f := excelize.NewFile()
// 文件对象最终要关闭,避免导出任务留下未释放的资源。
defer func() {
if err := f.Close(); err != nil {
log.Printf("关闭工作簿失败: %v", err)
}
}()
// Sheet1 是新工作簿自带的空白工作表,适合交给流式写入器。
sw, err := f.NewStreamWriter("Sheet1")
if err != nil {
log.Fatalf("创建流式写入器失败: %v", err)
}
// 列宽属于写行前的结构设置,先设置可以避免和后续行写入混用。
if err := sw.SetColWidth(1, 3, 18); err != nil {
log.Fatalf("设置列宽失败: %v", err)
}
headers := []interface{}{"编号", "名称", "状态"}
if err := sw.SetRow("A1", headers); err != nil {
log.Fatalf("写入表头失败: %v", err)
}
for rowID := 2; rowID
这段代码里最容易忽略的是两个顺序:SetRow 的行号从 1 往后递增,Flush 在 SaveAs 之前。写入单元格的值可以是普通值,也可以使用 Excelize 的 Cell 指定样式或公式;但不要因此把整张表重新组织成大对象后再写,流式收益仍然来自逐行准备数据。
Flush、SaveAs 和 Close 是三个不同的收尾动作
第一次接触流式 API 时,我很容易把“写完最后一行”当成“文件已经完成”。实际上,SetRow 只是把行交给流式写入器,Flush 才是结束流式写入过程;之后还要用 SaveAs 把工作簿保存成目标文件,最后由 Close 释放文件对象。
可以把它们分别理解成:
SetRow:持续追加一行,要求行号递增。Flush:结束当前工作表的流式写入。SaveAs:把已经完成的工作簿写到目标路径。Close:关闭工作簿对象,处理底层资源。
如果中途任何一个写入动作返回错误,就应该停止继续追加,并保留错误上下文。特别是不要在 Flush 失败后继续调用保存动作,把一个未完成的流误当成成功文件。
流式写入的几个限制,决定了它适不适合你

工作表要先处于适合流式写入的状态
NewStreamWriter 面向已经存在的空白工作表。新建工作簿自带 Sheet1,直接使用它最简单;如果要使用其他工作表,先创建或准备好工作表,再获取对应的流式写入器。不要把一个已经用普通模式写过大量内容的工作表直接当作空白流来处理。
不能把普通写入 API 和流式写入混在同一张表
官方文档明确提示,普通模式函数和流式模式函数不能混合用于同一张工作表的写入。这个限制意味着:需要设置的列宽、窗格或其他流式支持的结构选项,应在写行前处理;进入 SetRow 之后,就保持同一种写入模式,避免一半数据走 SetCellValue、另一半走 SetRow。
流式阶段不要依赖读回单元格
流式写入器会尽量把大块数据放到临时文件以减少内存压力,但官方 API 说明也指出,流式阶段不能读取单元格值。因此,校验业务数据应在调用 SetRow 前完成,或者在文件完成后另开读取流程;不要边写边用当前工作簿读取刚写入的值。
行号递增是接口约束,不是建议
如果数据来自分页查询或多个来源,先在应用层确定最终行号,再按从小到大的顺序调用 SetRow。不要为了插入一条迟到的数据回头写前面的行;需要重排时,先调整数据源输出顺序,再进入流式写入阶段。
我会怎样选择普通写入还是流式写入
| 场景 | 更合适的选择 | 判断理由 |
|---|---|---|
| 少量行、需要频繁修改单元格 | 普通写入 API | 代码更直观,修改和读取更灵活。 |
| 大量行、主要目标是顺序导出 | StreamWriter | 逐行准备数据,减少应用侧长期保留全部行。 |
| 同一张表既要回写又要边写边读 | 重新设计阶段边界 | 流式模式不适合和普通写入、读回动作混在一起。 |
| 需要表头、列宽和数据行 | 先做支持的结构设置,再 SetRow | 避免在流已经开始后改变写入模式。 |
对我来说,StreamWriter 的价值不是让任何 Excel 导出都更快,而是让“顺序生成大量行”这件事有清晰的资源边界。它的代价也很明确:要遵守递增行号、不能混用同表写入 API、不能依赖流式阶段的读取,还必须记得 Flush。
常见问题
Excelize 流式写入为什么一定要调用 Flush?
Flush 用来结束流式写入过程。只调用最后一次 SetRow 并不等于流已经收尾,应该在 SaveAs 之前检查 Flush 的错误。
SetRow 能不能乱序写入行号?
不建议,也不符合流式写入约束。应该让传给 SetRow 的行号保持递增;如果数据乱序,先在数据准备阶段排序或重新编号。
流式写入是否完全不占用内存?
不是。它减少的是应用长期保存全部待写行的压力,仍然会有当前行、工作簿对象和流式缓冲;Excelize 还可能在数据块较大时使用临时文件。应把它理解为更可控的写入方式,而不是零内存方案。
总结
Go Excelize 流式写入大量工作表行的核心写法是:对空白工作表调用 NewStreamWriter,按递增行号调用 SetRow,写完后调用 Flush,再 SaveAs 并关闭工作簿。只要把普通写入与流式写入的边界、读取限制和资源收尾记清楚,就能把大表导出从“把所有数据堆在内存里”改成更适合顺序生成的工作流。
Go go-toml 从 v1 树模型迁移到 v2 结构体解码要改什么
- 上一篇
- Go go-toml 从 v1 树模型迁移到 v2 结构体解码要改什么
- 下一篇
- Go go-toml 空值字段为什么仍被编码到 TOML
-
- Golang · Go教程 | 27分钟前 | 单元测试 · go ·
- Go sqlmock 怎么测试写入失败后的事务回滚
- 475浏览 收藏
-
- Golang · Go教程 | 28分钟前 |
- Go sqlmock 怎么检查查询结果集是否被关闭
- 133浏览 收藏
-
- Golang · Go教程 | 44分钟前 | 单元测试 · go · Go go-cmp cmpopts.IgnoreFields 生成时间 测试比较 cmp.Diff
- Go go-cmp 怎么在测试比较中忽略生成时间字段
- 363浏览 收藏
-
- Golang · Go教程 | 51分钟前 |
- Go csvutil 怎么逐条解码 CSV 而不保留所有记录
- 327浏览 收藏
-
- Golang · Go教程 | 52分钟前 |
- Go csvutil 怎么为业务字段实现自定义文本转换
- 326浏览 收藏
-
- Golang · Go教程 | 52分钟前 |
- Go csvutil 怎么让 CSV 表头映射到指定结构体字段
- 152浏览 收藏
-
- Golang · Go教程 | 58分钟前 |
- Go Excelize 怎么给单元格添加下拉数据校验
- 313浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go go-toml 怎么拒绝配置中未声明的字段
- 158浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go go-toml 怎么为业务类型实现自定义 TOML 解码
- 303浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 461次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 523次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 542次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 488次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 307次使用
-
- Java 性能优化上线清单:从定位、改造到灰度发布
- 2026-06-11 860浏览
-
- Spring Boot 压测验证:Gatling、JMeter 与性能回归门禁
- 2026-06-11 843浏览
-
- Java NMT 非堆内存排查:Direct Buffer、线程栈与 Metaspace 分析
- 2026-06-11 826浏览
-
- Spring Boot 容器内存优化:JVM 堆、非堆与 MaxRAMPercentage
- 2026-06-11 809浏览
-
- Tomcat 连接与线程参数调优:maxThreads、acceptCount 与 KeepAlive
- 2026-06-11 792浏览

