Golang 如何操作 excel xlsx(包含创建/修改/读取/常见问题)
在日常的后端开发工作中,我们经常会遇到需要处理 Excel 文件的场景,比如生成数据报表、读取用户上传的 Excel 数据进行批量处理等。那么如何在 Go 项目中高效地操作 Excel XLSX 文件呢?。本文将记录分享最近使用的一个操作 xlsx 的库,希望能帮你少走一些弯路。
一、使用 excelize 库
Go 语言本身没有内置的 Excel 处理功能,我们需要依赖第三方库。目前,处理 XLSX (Excel 2007 及以后版本) 最主流和成熟的库是 github.com/xuri/excelize/v2
注意:该库的 github 会自动重定向为:https://github.com/qax-os/excelize
excelize 功能强大,支持创建新 Excel 文件、读取现有文件、修改单元格内容、设置单元格样式、合并单元格、添加图表等多种操作。
安装
go get github.com/xuri/excelize/v2接下来,我们通过几个具体常用的例子来学习如何使用 excelize 进行常见的 Excel 操作。
二、创建并写入
这是最基本的操作。创建一个包含工作表、表头和一些数据的 Excel 文件
package main
import (
"fmt"
"github.com/xuri/excelize/v2"
)
func main() {
// 创建一个新的 Excel 文件
f := excelize.NewFile()
defer func() {
// 关闭文件
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
// 获取默认的工作表,默认名为 "Sheet1"
sheetName := "Sheet1"
// 或者,你也可以创建一个新的工作表
// newSheetName := "我的数据表"
// index, err := f.NewSheet(newSheetName)
// if err != nil {
// fmt.Println(err)
// return
// }
// f.SetActiveSheet(index) // 设置为活动工作表
// sheetName = newSheetName
// 设置表头
headers := []string{"姓名", "年龄", "城市", "邮箱"}
for col, header := range headers {
// Excel 的列是从 A 开始的,行是从 1 开始的
// 使用 excelize.CoordinatesToCellName 可以将行列号转换为单元格名称,如 (1,1) -> "A1"
cell, err := excelize.CoordinatesToCellName(col+1, 1)
if err != nil {
fmt.Println(err)
return
}
if err := f.SetCellValue(sheetName, cell, header); err != nil {
fmt.Println(err)
return
}
}
// 写入数据
data := [][]interface{}{
{"张三", 25, "北京", "zhangsan@example.com"},
{"李四", 30, "上海", "lisi@example.com"},
{"王五", 22, "广州", "wangwu@example.com"},
}
for row, rowData := range data {
for col, cellValue := range rowData {
// 数据从第 2 行开始写入
cell, err := excelize.CoordinatesToCellName(col+1, row+2)
if err != nil {
fmt.Println(err)
return
}
if err := f.SetCellValue(sheetName, cell, cellValue); err != nil {
fmt.Println(err)
return
}
}
}
// 保存文件
if err := f.SaveAs("example.xlsx"); err != nil {
fmt.Println(err)
} else {
fmt.Println("Excel 文件创建成功!")
}
}
excelize.NewFile(): 创建一个新的空白 Excel 文件对象。f.NewSheet("表名"): 创建一个新的工作表。f.SetCellValue(sheetName, cell, value): 向指定工作表的指定单元格写入值。cell可以是 “A1” 这样的字符串,也可以通过excelize.CoordinatesToCellName(row, col)转换得到。f.SaveAs("文件名.xlsx"): 将文件保存到指定路径。
三、读取 excel 文件
以下例子是读取上一步创建的 example.xlsx 文件中的数据:
package main
import (
"fmt"
"github.com/xuri/excelize/v2"
)
func main() {
// 打开一个现有的 Excel 文件
f, err := excelize.OpenFile("example.xlsx")
if err != nil {
fmt.Println(err)
return
}
defer func() {
// 关闭文件
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
// 获取所有工作表名称
sheetNames := f.GetSheetMap()
fmt.Println("工作表名称列表:", sheetNames)
// 选择要读取的工作表,这里我们读取第一个工作表
sheetName := sheetNames[1] // sheetNames 是 map[int]string, key 是索引,从 1 开始
fmt.Printf("正在读取工作表: %s\n", sheetName)
// 获取工作表的最大行数和列数
// 注意:GetRows 会自动忽略空行,但 GetSheetDimension 会返回包含空行的范围
// rows, err := f.GetRows(sheetName)
// if err != nil {
// fmt.Println(err)
// return
// }
// fmt.Printf("工作表 %s 共有 %d 行数据\n", sheetName, len(rows))
// 使用 GetSheetDimension 获取更准确的范围
dimension, err := f.GetSheetDimension(sheetName)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("工作表维度: %s\n", dimension) // 例如:A1:D4
// 按行读取数据
rows, err := f.GetRows(sheetName, excelize.Options{RawCellValue: false})
if err != nil {
fmt.Println(err)
return
}
for rowIndex, row := range rows {
fmt.Printf("第 %d 行: ", rowIndex+1)
for colIndex, cellValue := range row {
// cellValue 是字符串类型
fmt.Printf("列%d:%s ", colIndex+1, cellValue)
}
fmt.Println()
}
// 也可以直接读取指定单元格的值
cellValue, err := f.GetCellValue(sheetName, "A2")
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("单元格 A2 的值是: %s\n", cellValue) // 应该输出 "张三"
}
excelize.OpenFile("文件名.xlsx"): 打开一个已存在的 Excel 文件。f.GetSheetMap(): 获取所有工作表的名称及其索引。f.GetRows(sheetName): 获取指定工作表的所有行数据,返回一个二维字符串切片。excelize.Options{RawCellValue: false}表示获取单元格格式化后的值。f.GetCellValue(sheetName, cell): 获取指定单元格的值。
四、修改 excel 内容
在读取文件的基础上,修改其中的数据并保存:
package main
import (
"fmt"
"github.com/xuri/excelize/v2"
)
func main() {
// 打开一个现有的 Excel 文件
f, err := excelize.OpenFile("example.xlsx")
if err != nil {
fmt.Println(err)
return
}
defer func() {
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
sheetName := "Sheet1"
// 修改一个单元格的值
if err := f.SetCellValue(sheetName, "D3", "updated_lisi@example.com"); err != nil {
fmt.Println(err)
return
}
fmt.Println("单元格 D3 (李四的邮箱) 已修改。")
// 增加一行新数据
newRow := []interface{}{"赵六", 28, "深圳", "zhaoliu@example.com"}
// 获取当前数据的最后一行
rows, err := f.GetRows(sheetName)
if err != nil {
fmt.Println(err)
return
}
nextRow := len(rows) + 1
for col, cellValue := range newRow {
cell, err := excelize.CoordinatesToCellName(col+1, nextRow)
if err != nil {
fmt.Println(err)
return
}
if err := f.SetCellValue(sheetName, cell, cellValue); err != nil {
fmt.Println(err)
return
}
}
fmt.Println("已新增一行数据 (赵六)。")
// 保存修改后的文件,可以覆盖原文件,也可以保存为新文件
if err := f.SaveAs("example_modified.xlsx"); err != nil {
fmt.Println(err)
} else {
fmt.Println("Excel 文件修改并保存成功!")
}
}这个例子是在
OpenFile的基础上,使用SetCellValue修改了D3单元格的值,并在数据末尾添加了一行新数据,最后用SaveAs保存为新文件
常见问题
1. 中文乱码
问:写入 Excel 的中文字符显示为 ? 或乱码
解:在写入中文前,为单元格或样式设置支持中文的字体,如 “SimSun” (宋体), “Microsoft YaHei” (微软雅黑)
// 示例:创建一个样式并设置中文字体
style, err := f.NewStyle(`{"font":{"name":"SimSun","size":12}}`)
if err != nil {
fmt.Println(err)
return
}
// 将样式应用到单元格 A1
if err := f.SetCellStyle(sheetName, "A1", "A1", style); err != nil {
fmt.Println(err)
return
}2. 数字格式问题(如小数、日期)
问:写入的数字(如 123.45)在 Excel 中显示为科学计数法或文本格式;写入的日期(如 time.Now())显示为数字
解:使用 SetCellValue 传入正确类型的值(如 float64(123.45) 而不是字符串 "123.45")。对于日期,可以先格式化,或者使用 SetCellFormula 设置公式,或者通过样式设置数字格式
// 示例:设置日期格式
dateStyle, err := f.NewStyle(`{"number_format": 22}`) // 22 对应 'm/d/yyyy' 格式,也可以用自定义格式如 `"yyyy-mm-dd"`
if err != nil {
fmt.Println(err)
return
}
f.SetCellValue(sheetName, "E2", time.Now())
f.SetCellStyle(sheetName, "E2", "E2", dateStyle)3. 大文件处理时内存占用过高
问:处理包含大量行和列的 Excel 文件时,程序内存占用急剧上升,甚至 OOM
解:考虑使用流式读写。excelize 从 v2.4.0 版本开始提供了 OpenReader 和 StreamingReader 支持流式读取,如果只是读取数据,可以考虑使用更底层的库或者分块处理,
对于写入大文件,excelize 的性能通常不错,但也要注意数据构造的方式,避免在内存中创建过大的切片。
4. 合并单元格的读取问题
问:读取合并单元格区域时,只有左上角的单元格有值,其他合并的单元格为空
解:这是正常的,需要开发者在读取时,先检测单元格是否被合并 (f.GetMergeCells(sheetName)),然后根据合并区域来获取值
5. 文件保存后无法打开或提示损坏
问:程序运行没有报错,但生成的 Excel 文件无法用 Excel 或 WPS 打开,或打开时提示文件已损坏
解:检查 SaveAs 的返回错误,确保文件被正确保存和关闭。如果怀疑是库的问题,可以尝试更新 excelize 到最新版本
一般都是程序代码写入存在问题
总结
excelize 的功能远不止于此,它还支持设置单元格样式、边框、填充色、添加图片、创建图表等高级功能。你可以查阅其官方文档来获取更详细的信息,希望本文对你的开发有所帮助,欢迎大家在评论区留言交流!
版权声明
未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!
本文原文链接: https://fiveyoboy.com/articles/go-xlsx-excelize/
备用原文链接: https://blog.fiveyoboy.com/articles/go-xlsx-excelize/