Go 文件与JSON:把任务写入磁盘并读回验证
完成对象编码、文件写入、读取与解码的完整小流程,逐步检查错误,并明确文件路径与数据格式分别承担的职责。
本节目标
完成对象编码、文件写入、读取与解码的完整小流程,逐步检查错误,并明确文件路径与数据格式分别承担的职责。
前置知识
了解结构体、函数和error处理。示例使用临时目录,不会覆盖当前目录中的同名文件。
准备与运行
在一个新的练习目录执行以下命令。目录名可以修改;若该目录已经初始化过模块,不要重复执行go mod init。
mkdir go-lesson-11
cd go-lesson-11
go mod init example.com/devcn/lesson11将下方完整代码保存为当前目录的main.go,然后执行:
go fmt ./...
go run .先用go version确认工具链可用。本例在Go 1.25.7、macOS ARM64环境验证,只使用标准库;没有测试所有操作系统和Go版本。
核心概念
内存中的结构体不能直接当作文件内容写入。json.Marshal把导出的字段编码为字节;字段标签决定JSON键名,Title使用title作为外部名称。os.WriteFile负责把这些字节保存到指定路径,两步各自可能失败。
读取时执行相反过程:先从磁盘取得字节,再用json.Unmarshal解析到目标变量。解码传入&task,是为了让函数更新这个变量,而不是只拿到它的值副本。
示例用MkdirTemp创建独立目录,并用defer安排清理。路径通过filepath.Join组合,减少手工拼接分隔符造成的平台差异。
完整示例
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
)
type Task struct {
Title string `json:"title"`
}
func run() error {
dir, err := os.MkdirTemp("", "devcn-go-")
if err != nil {
return err
}
defer os.RemoveAll(dir)
data, err := json.Marshal(Task{Title: "复习"})
if err != nil {
return err
}
path := filepath.Join(dir, "task.json")
if err = os.WriteFile(path, data, 0600); err != nil {
return err
}
data, err = os.ReadFile(path)
if err != nil {
return err
}
var task Task
if err = json.Unmarshal(data, &task); err != nil {
return err
}
fmt.Println(task.Title)
return nil
}
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}运行后应得到:
复习代码如何工作
run函数负责整个流程,出现错误立即向调用方返回。只有所有步骤成功,才打印从文件恢复的Title。这样可以确认输出确实经过了编码、磁盘和解码,而不是直接打印原对象。
main负责处理最终失败:向标准错误输出信息,并使用非零退出码结束。defer放在run内,所以run返回时先进行目录清理,随后main才可能执行os.Exit。
写入时指定0600权限,主要用于类Unix系统上限制新文件的访问;不要把它当作跨平台访问控制的完整实现。示例只处理一个很小的文件,适合先理解API调用顺序。
常见错误与边界
- 结构体字段小写开头时,默认JSON编码不会将它作为导出字段处理。
- 无效JSON应返回错误,不要忽略后使用可能尚未正确填充的对象。
- 整文件读写会把数据放入内存,大文件需要考虑流式处理。
- WriteFile不是事务系统,也不自动提供故障时的原子替换保证;生产配置另需考虑备份、临时文件替换和并发写入。
练习
增加Done布尔字段并设为true;在读回后同时打印Title与Done,再用无效JSON测试失败路径。
参考答案
字段定义改为:
type Task struct {
Title string `json:"title"`
Done bool `json:"done"`
}编码输入使用Task{Title: "复习", Done: true},读回后fmt.Println(task.Title, task.Done)应得到“复习 true”。
另用json.Unmarshal([]byte("{"), &task)检查无效输入:应得到非nil错误;不要继续把task当作有效数据。
文章回复
0 条公开回复