文件操作
文件处理常用核心包:os(创建/读写/删除/权限)、io(通用 Reader/Writer 接口)、bufio(缓冲读写,性能优化)、filepath(跨平台路径)。
ioutil 包在 Go 1.16 已弃用,其功能迁移到 os(os.ReadFile、os.WriteFile)和 io(io.ReadAll),新代码直接用 os 和 io。
创建与打开文件
| 函数 | 模式 | 说明 |
|---|---|---|
| os.Open(name) | 只读 | 最简单,只读打开 |
| os.Create(name) | 读写 + 创建/截断 | 创建新文件,已存在则清空 |
| os.OpenFile(name, flag, perm) | 自定义 | 可指定读写、追加、创建等标志 |
package main
import (
"log"
"os"
)
func main() {
// os.Create 创建文件(已存在则清空)
file, err := os.Create("test.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close() // defer 确保函数结束时关闭文件
file.WriteString("文件创建成功\n")
}
defer file.Close() 是 Go 文件操作的最佳实践。即使后续代码 panic,文件也会被正确关闭,避免文件描述符泄漏。
OpenFile 标志位
| 标志 | 说明 |
|---|---|
| os.O_RDONLY | 只读(默认) |
| os.O_WRONLY | 只写 |
| os.O_RDWR | 读写 |
| os.O_APPEND | 追加模式,写入内容追加到文件末尾 |
| os.O_CREATE | 文件不存在时创建 |
| os.O_TRUNC | 打开时清空文件内容 |
| os.O_EXCL | 与 CREATE 配合,文件已存在时报错 |
文件读取
三种读取方式,按文件大小选择:一次性 os.ReadFile(小文件)、逐行 bufio.Scanner(大文件)、流式 io.ReadAll(已打开的 Reader)。
一次性读取
package main
import (
"fmt"
"log"
"os"
)
func main() {
// 自动打开、读取、关闭,一步到位
data, err := os.ReadFile("config.json")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
}
逐行读取大文件
package main
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {
file, err := os.Open("large-file.log")
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
lineNum := 0
for scanner.Scan() {
lineNum++
fmt.Printf("第 %d 行: %s\n", lineNum, scanner.Text())
}
if err := scanner.Err(); err != nil {
log.Fatal("读取错误:", err)
}
}
bufio.Scanner 默认最大行长度为 64KB,超长行需在 Scan() 前调用 scanner.Buffer(make([]byte, 0), maxSize) 增大缓冲区。
文件写入
一次性写入
package main
import (
"log"
"os"
)
func main() {
content := []byte("Hello, Go!\n这是第二行\n")
// 0644: 所有者读写,其他用户只读
err := os.WriteFile("output.txt", content, 0644)
if err != nil {
log.Fatal(err)
}
log.Println("写入成功")
}
缓冲写入(大量数据推荐)
bufio.Writer 先把数据写入内存缓冲区,攒够后再批量写盘,减少 I/O 次数。结束前必须调用 Flush()。
package main
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {
file, err := os.Create("buffered-output.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
writer := bufio.NewWriter(file)
for i := 0; i < 1000; i++ {
writer.WriteString("第 " + fmt.Sprintf("%d", i) + " 行\n")
}
// Flush 将缓冲区剩余数据写入文件
if err := writer.Flush(); err != nil {
log.Fatal("刷新缓冲区失败:", err)
}
log.Println("缓冲写入完成")
}
追加写入
日志场景用 O_APPEND 追加,每次运行都不会覆盖已有内容。
package main
import (
"log"
"os"
"time"
)
func main() {
// O_APPEND: 追加模式
// O_CREATE: 不存在则创建
// O_WRONLY: 只写
file, err := os.OpenFile("app.log",
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
timestamp := time.Now().Format("2006-01-02 15:04:05")
file.WriteString("[" + timestamp + "] 应用启动\n")
}
对比:os.Create 会清空已有内容;os.OpenFile 配合 O_APPEND 会追加内容。
判断文件是否存在
Go 没有专门的"文件存在"函数,通过 os.Stat + os.IsNotExist 判断:
package main
import (
"fmt"
"os"
)
func fileExists(path string) bool {
_, err := os.Stat(path)
return !os.IsNotExist(err)
}
func main() {
if fileExists("test.txt") {
fmt.Println("文件存在")
} else {
fmt.Println("文件不存在")
}
}
文件信息与权限
package main
import (
"fmt"
"log"
"os"
)
func main() {
info, err := os.Stat("test.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println("文件名:", info.Name())
fmt.Println("大小:", info.Size(), "字节")
fmt.Println("权限:", info.Mode())
fmt.Println("修改时间:", info.ModTime())
fmt.Println("是目录吗:", info.IsDir())
}
权限说明:0644 表示所有者可读写(6=4+2),组和其他用户只读(4)。目录通常用 0755(多了执行权限,否则无法进入目录)。
场景速查表
| 场景 | 推荐方式 | 关键代码 |
|---|---|---|
| 读取小文件 | os.ReadFile | data, err := os.ReadFile("file.txt") |
| 逐行读取大文件 | bufio.Scanner | scanner := bufio.NewScanner(file) |
| 写入小文件 | os.WriteFile | os.WriteFile("f.txt", data, 0644) |
| 大量数据写入 | bufio.Writer | writer := bufio.NewWriter(file) |
| 追加日志 | os.OpenFile + O_APPEND | os.OpenFile("log", os.O_APPEND |
| 文件复制 | io.Copy | io.Copy(dst, src) |
| 判断文件存在 | os.Stat + os.IsNotExist | _, err := os.Stat(path) |
| 路径拼接 | filepath.Join | filepath.Join("dir", "file.txt") |
跨平台路径拼接永远用 filepath.Join("dir", "file.txt"),不要用字符串拼接 "dir" + "/" + "file",Windows 上分隔符不同。