文件操作
2026年9月6日
文件 I/O 主要在 os、io、bufio。小文件一次读完,大文件按块或按行读。
文件读取
一次性读取
b, err := os.ReadFile("a.txt")
if err != nil {
return err
}
fmt.Println(string(b))
go
整份进内存,配置、短文本够用。
获取当前 go 文件的路径
相对路径是相对进程工作目录,不是相对 .go 源文件。要用源文件旁边的数据文件,先拿到这个文件自己的路径:
_, file, _, _ := runtime.Caller(0)
dir := filepath.Dir(file)
p := filepath.Join(dir, "a.txt")
b, err := os.ReadFile(p)
go
编译进二进制之后,源码路径不一定还在,正式项目更常把文件放进 embed 或按工作目录约定。
分片读
自己准备一块 buffer,循环 Read,直到 io.EOF:
f, err := os.Open("a.txt")
if err != nil {
return err
}
defer f.Close()
buf := make([]byte, 1024)
for {
n, err := f.Read(buf)
if n > 0 {
fmt.Print(string(buf[:n]))
}
if err == io.EOF {
break
}
if err != nil {
return err
}
}
go
带缓冲读
bufio.Scanner 默认按行。大文件、日志常用:
sc := bufio.NewScanner(f)
for sc.Scan() {
fmt.Println(sc.Text())
}
if err := sc.Err(); err != nil {
return err
}
go
换分隔符:sc.Split(bufio.ScanWords),或自己写 SplitFunc。单行默认上限大约 64KB,超长行要加大 buffer。
文件写入
一次性写
err := os.WriteFile("b.txt", []byte("hello\n"), 0644)
go
存在就覆盖。
文件的打开方式
细控制用 os.OpenFile:
| 标志 | 含义 |
|---|---|
os.O_RDONLY | 只读 |
os.O_WRONLY | 只写 |
os.O_RDWR | 读写 |
os.O_CREATE | 不存在就创建 |
os.O_TRUNC | 打开时清空 |
os.O_APPEND | 追加 |
f, err := os.OpenFile("b.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return err
}
defer f.Close()
_, err = f.WriteString("一行\n")
go
完整模式就是这些标志按位或。只读打开已有文件用 os.Open。
文件的权限
第三个参数是 Unix 权限,Windows 上基本忽略。三位八进制:所有者、组、其他人,每位是读(4)+写(2)+执行(1)。
| 值 | 含义 |
|---|---|
0644 | 所有者读写,其他人只读 |
0755 | 所有者可执行,其他人可读可执行 |
0600 | 只有所有者读写 |
0644 就是 rw-r--r--。
文件复制
src, err := os.Open("a.txt")
if err != nil {
return err
}
defer src.Close()
dst, err := os.Create("b.txt")
if err != nil {
return err
}
defer dst.Close()
_, err = io.Copy(dst, src)
go
os.Create 等于 O_RDWR|O_CREATE|O_TRUNC。
目录操作
os.Mkdir("data", 0755)
os.MkdirAll("a/b/c", 0755) // 中间不存在也会建
entries, err := os.ReadDir(".")
for _, e := range entries {
fmt.Println(e.Name(), e.IsDir())
}
os.Remove("b.txt") // 文件或空目录
os.RemoveAll("a") // 整棵删掉,小心
go
判断存在:os.Stat,errors.Is(err, os.ErrNotExist)。