os.WriteFile适合小到中等二进制数据写入,大文件或需追加时应使用os.OpenFile配合Write;写结构体须用binary.Write等序列化方式,避免unsafe.Pointer;务必检查f.Close()错误以确保落盘。

用 os.WriteFile 写入二进制数据最简单
只要数据已经是 []byte 类型,os.WriteFile 就是最直接的选择。它底层调用系统调用一次写完,适合小到中等体积的二进制内容(比如图片、序列化结构体、加密密钥等)。
常见错误是传入字符串误当二进制:比如 "\x00\x01" 是字符串字面量,Go 会按 UTF-8 编码,可能被转义或截断;必须显式构造 []byte:
data := []byte{0x00, 0x01, 0xff}
err := os.WriteFile("out.bin", data, 0644)
- 权限参数
0644在 Windows 上会被忽略,但建议保留以保持跨平台一致性 - 如果文件已存在,
os.WriteFile会覆盖,不追加 - 写入超大文件(如 >100MB)时,一次性加载到内存可能引发 OOM,此时应换流式写法
大文件或需要追加时用 os.OpenFile + Write
当你要写入 GB 级二进制数据、边生成边写、或需追加到现有文件末尾,就得绕过 os.WriteFile,手动控制文件句柄。
关键点在于打开模式和写入方式:
立即学习“go语言免费学习笔记(深入)”;
f, err := os.OpenFile("large.bin", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
// handle
}
defer f.Close()
// 写入一段二进制
n, err := f.Write([]byte{0xde, 0xad, 0xbe, 0xef})
-
os.O_APPEND保证每次Write都追加到文件末尾,即使有多个 goroutine 并发写,内核也保证原子性(POSIX 行为) - 如果要覆盖写(非追加),去掉
os.O_APPEND,并用f.Seek(0, io.SeekStart)和f.Truncate(0)清空文件 - 注意
Write不保证一次写完全部字节,返回值n可能小于len(p),生产环境应检查并循环写(io.WriteFull或封装工具函数)
写结构体二进制时别直接 unsafe.Pointer
有人想用 *(*[size]byte)(unsafe.Pointer(&myStruct)) 把 struct 直接转成字节——这很危险,因为 Go struct 默认不保证内存布局连续(有 padding)、字段顺序受编译器优化影响、且跨平台/跨版本不稳定。
正确做法是用标准库序列化:
- 简单固定结构:用
binary.Write(需实现io.Writer接口,可传*os.File) - 带版本或兼容性需求:用
encoding/gob(Go 原生二进制格式,但仅限 Go 间通信) - 跨语言或长期存储:用
encoding/json(文本)或encoding/xml,或者更紧凑的protobuf/msgpack
例如用 binary.Write 写一个 int32:
f, _ := os.Create("num.bin")
defer f.Close()
binary.Write(f, binary.LittleEndian, int32(0x12345678))
注意文件关闭和错误检查的时机
写二进制文件时,真正的落盘可能延迟在 Close() 时发生(尤其在 Linux ext4 默认挂载选项下)。如果只检查 Write 的返回值就认为写成功,而忽略 Close() 错误,可能丢失数据。
- 必须检查
f.Close()返回的err,它可能包含 write-back 失败(如磁盘满、I/O 错误) - 不要用
defer f.Close()后再做其他可能失败的操作(比如重命名),否则Close()错误会被掩盖 - 若需原子写入,先写到临时文件(如
out.bin.tmp),写完Close()无错后再os.Rename()
二进制写入本身不复杂,但“写完没报错”不等于“数据已安全落盘”,这个边界最容易被忽略。


















