文件存储(二):通过 CSV 格式读写文本数据

在上篇教程中,学院君给大家演示了如何通过 JSON 编码存储文本数据到磁盘文件,除此之外,Go 语言还提供了对 CSV 格式文件的支持,CSV 文件本质上虽然就是文本格式数据,不过可以兼容 Excel 表格,这样一来就可以极大方便我们对大批量数据进行管理。

使用 encoding/csv 包读写 CSV 文件

encoding/csvcsv.go
package main

import (
    "encoding/csv"
    "fmt"
    "os"
    "strconv"
)

type Tutorial struct {
    Id int
    Title string
    Summary string
    Author string
}

func main()  {
    // 创建一个 tutorials.csv 文件
    csvFile, err := os.Create("tutorials.csv")
    if err != nil {
        panic(err)
    }
    defer csvFile.Close()

    // 初始化字典数据
    tutorials := []Tutorial{
        Tutorial{Id: 1, Title: "Go 入门编程", Summary: "Go 基本语法和使用示例", Author: "学院君"},
        Tutorial{Id: 2, Title: "Go Web 编程", Summary: "Go Web 编程入门指南", Author: "学院君"},
        Tutorial{Id: 3, Title: "Go 并发编程", Summary: "通过并发编程提升性能", Author: "学院君"},
        Tutorial{Id: 4, Title: "Go 微服务开发", Summary: "基于 go-micro 框架开发微服务", Author: "学院君"},
    }

    // 初始化一个 csv writer,并通过这个 writer 写入数据到 csv 文件
    writer := csv.NewWriter(csvFile)
    for _, tutorial := range tutorials {
        line := []string{
            strconv.Itoa(tutorial.Id),  // 将 int 类型数据转化为字符串
            tutorial.Title,
            tutorial.Summary,
            tutorial.Author,
        }
        // 将切片类型行数据写入 csv 文件
        err := writer.Write(line)
        if err != nil {
            panic(err)
        }
    }
    // 将 writer 缓冲中的数据都推送到 csv 文件,至此就完成了数据写入到 csv 文件
    writer.Flush()

    // 打开这个 csv 文件
    file, err := os.Open("tutorials.csv")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    // 初始化一个 csv reader,并通过这个 reader 从 csv 文件读取数据
    reader := csv.NewReader(file)
    // 设置返回记录中每行数据期望的字段数,-1 表示返回所有字段
    reader.FieldsPerRecord = -1
    // 通过 readAll 方法返回 csv 文件中的所有内容
    record, err := reader.ReadAll()
    if err != nil {
        panic(err)
    }

    // 遍历从 csv 文件中读取的所有内容,并将其追加到 tutorials2 切片中
    var tutorials2 []Tutorial
    for _, item := range record {
        id, _ := strconv.ParseInt(item[0], 0, 0)
        tutorial := Tutorial{Id: int(id), Title: item[1], Summary: item[2], Author: item[3]}
        tutorials2 = append(tutorials, tutorial)
    }

    // 打印 tutorials2 的第一个元素验证 csv 文件写入/读取是否成功
    fmt.Println(tutorials2[0].Id)
    fmt.Println(tutorials2[0].Title)
    fmt.Println(tutorials2[0].Summary)
    fmt.Println(tutorials2[0].Author)
}

可以看到新建文件、打开文件、关闭文件和上篇教程操作普通的磁盘文件并无区别,不过这里为了支持通过 CSV 格式写入和读取文件,我们在文件句柄之上套了一层 CSV Writer 和 CSV Reader,这有点像适配器模式,然后我们就可以通过 CSV Writer 写入数据到 CSV 文件,通 过 CSV Reader 读取 CSV 文件了:

...

// 初始化一个 csv writer,并通过这个 writer 写入数据到 csv 文件
writer := csv.NewWriter(csvFile)

...

// 初始化一个 csv reader,并通过这个 reader 从 csv 文件读取数据
reader := csv.NewReader(file)  

...
encoding

使用方法了操作 CSV 文件一致,这也是 Go 语言设计之美的体现,通过接口与组合的方式可以很方便的构建起复杂的业务功能,感兴趣的同学可以去看下 Go 官方的 io 包实现源码。

osioutilioutil

关于上述代码的实现细节,都已经通过详细的注释标注了,我们重点关注如何将数据写入 CSV 文件,以及如何从 CSV 文件读取数据即可。

运行上述代码,返回结果如下,说明 CSV 文件写入和读取成功:

当然,你也可以在此基础上扩展出 CSV 文件数据的增删改查功能,感兴趣的同学可以自己尝试下,这里就具体展开了。

使用不同软件预览 CSV 文件

csv.gotutorials.csv

在 Mac 系统中,你可以通过 Numbers 应用打开这个文件进行预览,格式化后的数据就好看多了:

在 Windows 中,可以通过 Excel 软件打开这个文件,但是现在看到的是乱码数据:

这是因为 Excel 默认并不是 UTF-8 编码,因此要解决这个乱码问题,可以在对应的 CSV 文件写入 UTF-8 BOM 头,告知 Excel 通过 UTF-8 编码打开这个文件:

...

// 写入 UTF-8 BOM,防止中文乱码
csvFile.WriteString("\xEF\xBB\xBF")

// 初始化一个 csv writer,并通过这个 writer 写入数据到 csv 文件
writer := csv.NewWriter(csvFile)

...
tutorials.csv

关于 Excel 文件的读取和写入,学院君就简单介绍到这里,这里留一个课后作业,参考 encoding/csv 包读写 CSV 文件的方式,试着编写一段使用 encoding/json 包读写 JSON 文件的代码并正常运行起来,看看生成的文件是否符合预期。