Go文件操作需严格检查error、及时close、分块读写防OOM、Write后校验n值、关键场景调用Sync。忽略任一环节均可能导致panic、fd泄漏、内存溢出或数据丢失。

open() 返回 *os.File 但忽略 err 判断
Go 的 os.Open、os.Create、os.OpenFile 都返回 (*os.File, error),常见错误是只取第一个值却没检查 err 是否为 nil。一旦路径不存在、权限不足或磁盘满,*os.File 是 nil,后续调用 Read() 或 Write() 会 panic。
正确做法始终先判断 err:
file, err := os.Open("config.txt")
if err != nil {
log.Fatal("failed to open file:", err) // 或 return err
}
defer file.Close()
- 不要用
if file == nil替代err != nil——os.Open在出错时可能返回非nil的*os.File(如部分系统调用成功但元数据读取失败) - 使用
errors.Is(err, os.ErrNotExist)区分具体错误类型,便于差异化处理(比如缺配置文件时加载默认值)
忘记 close() 或 defer 放错位置
os.File 是对系统文件描述符的封装,不显式 Close() 会导致 fd 泄漏,进程最终因“too many open files”崩溃。常见陷阱包括:
-
defer file.Close()写在err != nil分支之后 —— 错误时file为nil,调用Close()panic - 循环中反复
os.Open却只在循环外defer—— 实际只关闭最后一次打开的文件 - 用
bufio.NewReader(file)后,误以为 reader 负责关闭底层 file —— 它不会
安全写法:在确认 file != nil 且 err == nil 后立即 defer:
立即学习“go语言免费学习笔记(深入)”;
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // 此时 f 必然非 nil
ReadAll() 在大文件上触发 OOM
ioutil.ReadAll()(Go 1.16+ 推荐用 io.ReadAll())会把整个文件读进内存。对几百 MB 以上的文件,极易导致程序被系统 kill。
- 替代方案:用
os.Open+bufio.Scanner按行处理,或io.CopyN()/io.ReadFull()分块读取 - 若必须全读,先用
os.Stat().Size()检查大小,超阈值(如 10MB)则拒绝或走流式路径 -
io.ReadAll()返回的[]byte和原file无绑定关系 —— 关闭文件不影响已读数据,但别指望它自动释放底层资源
WriteString() / Write() 不检查返回的 n 和 err
Write() 系列方法返回 (int, error),其中 int 是实际写入字节数。常见错误是只判 err,忽略 n 是否等于预期长度。
- 网络文件系统(如 NFS)、满磁盘、信号中断等场景下,
Write()可能只写入部分数据并返回nil错误 —— 这是合法行为,需循环重试或用io.WriteString()(它内部已做补全) -
fmt.Fprint()等包装函数也返回(int, error),同样不能只看err - 写日志或关键配置时,建议用
file.Sync()强制刷盘,否则程序崩溃可能导致最后几 KB 数据丢失
文件 I/O 的错误不是“有没有”,而是“在哪一层漏掉”。最常被跳过的其实是 Close() 的返回值 —— 它也可能报错(比如写缓冲区 flush 失败),但很少有人检查。










