IO 与文件系统
包总览
| 包 | 核心接口/函数 | 用途 |
|---|
io | Reader, Writer, Copy, ReadAll | IO 基础抽象 |
io/fs | FS, File, DirEntry | 文件系统抽象 |
os | Open, Create, Mkdir, Exit | 操作系统交互 |
path/filepath | Join, Clean, Walk, Glob | 跨平台路径处理 |
bufio | Scanner, Reader, Writer | 缓冲 IO |
archive/tar | 读写 tar 归档 | tar 文件处理 |
archive/zip | 读写 zip 归档 | zip 文件处理 |
compress/gzip | gzip 压缩/解压 | gzip 流处理 |
io 包
核心接口
// Reader:读取数据
type Reader interface {
Read(p []byte) (n int, err error)
}
// Writer:写入数据
type Writer interface {
Write(p []byte) (n int, err error)
}
// Closer:关闭资源
type Closer interface {
Close() error
}
// Seeker:随机定位
type Seeker interface {
Seek(offset int64, whence int) (int64, error)
}
// ReadWriter = Reader + Writer
// ReadCloser = Reader + Closer
// ReadWriteCloser = Reader + Writer + Closer
// ReadWriteSeeker = 全部
io 常用函数
// io.Copy:从 Reader 复制到 Writer(高效,零拷贝优化)
n, err := io.Copy(dst, src)
// io.CopyN:复制 N 字节
n, err := io.CopyN(dst, src, 1024)
// io.ReadAll:读取全部内容
data, err := io.ReadAll(reader)
// io.LimitReader:限制读取长度
limited := io.LimitReader(reader, 1024) // 最多读 1024 字节
// io.MultiReader:串联多个 Reader
combined := io.MultiReader(r1, r2, r3) // 依次读 r1→r2→r3
// io.MultiWriter:同时写入多个 Writer
tee := io.MultiWriter(w1, w2) // 写入同时复制到 w1 和 w2
// io.TeeReader:读取的同时写入(类似 tee 命令)
tee := io.TeeReader(src, dst) // 从 src 读,同时写一份到 dst
// io.Pipe:创建同步管道
r, w := io.Pipe() // 写入 w 的数据可以从 r 读出
io 实用实现
// strings.Reader:把 string 当 Reader
r := strings.NewReader("hello world")
// bytes.Buffer:可读可写的缓冲区
buf := bytes.NewBuffer(nil)
buf.WriteString("hello")
buf.Write([]byte(" world"))
data := buf.Bytes() // hello world
// bytes.Reader:把 []byte 当 Reader
r := bytes.NewReader([]byte{0x01, 0x02, 0x03})
// io.Discard:丢弃所有写入(类似 /dev/null)
io.Copy(io.Discard, reader) // 读取并丢弃
os 包
文件操作
// 打开文件(只读)
f, err := os.Open("config.yaml")
if err != nil {
log.Fatal(err)
}
defer f.Close()
// 打开文件(指定权限和标志)
f, err := os.OpenFile("app.log",
os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
// O_RDONLY 只读
// O_WRONLY 只写
// O_RDWR 读写
// O_CREATE 不存在则创建
// O_TRUNC 打开时截断
// O_APPEND 追加写入
// O_EXCL 与 O_CREATE 一起使用,文件必须不存在
if err != nil {
log.Fatal(err)
}
defer f.Close()
// 创建文件(等价于 OpenFile with O_CREATE|O_WRONLY|O_TRUNC, 0666)
f, err := os.Create("output.txt")
// 读取
data, err := os.ReadFile("config.yaml") // Go 1.16+,一次性读取
// 写入
err := os.WriteFile("output.txt", []byte("hello"), 0644) // Go 1.16+
// 获取文件信息
info, err := os.Stat("config.yaml")
fmt.Println(info.Name()) // config.yaml
fmt.Println(info.Size()) // 文件大小(字节)
fmt.Println(info.Mode()) // 文件权限
fmt.Println(info.ModTime()) // 修改时间
fmt.Println(info.IsDir()) // 是否目录
// 判断文件是否存在
if _, err := os.Stat("file.txt"); err != nil {
if os.IsNotExist(err) {
// 文件不存在
}
}
// Go 1.13+ 更简洁:
if errors.Is(err, fs.ErrNotExist) {
// 文件不存在
}
// 删除文件
err := os.Remove("file.txt")
// 重命名/移动
err := os.Rename("old.txt", "new.txt")
目录操作
// 创建目录
os.Mkdir("dir", 0755) // 创建单层
os.MkdirAll("a/b/c", 0755) // 递归创建多层
// 读取目录
entries, err := os.ReadDir(".") // Go 1.16+,返回 []fs.DirEntry
for _, entry := range entries {
fmt.Println(entry.Name())
info, _ := entry.Info()
fmt.Println(info.Size())
if entry.IsDir() {
fmt.Println("directory")
}
}
// 临时文件
tmpFile, err := os.CreateTemp("", "prefix-*.txt")
defer os.Remove(tmpFile.Name())
defer tmpFile.Close()
// 临时目录
tmpDir, err := os.MkdirTemp("", "myapp-*")
defer os.RemoveAll(tmpDir)
环境变量
// 读取
val := os.Getenv("HOME") // 不存在返回 ""
val, exists := os.LookupEnv("DATABASE_URL") // 检查是否存在
// 设置
os.Setenv("DEBUG", "true")
// 取消
os.Unsetenv("DEBUG")
// 列出所有
for _, env := range os.Environ() {
fmt.Println(env) // "KEY=VALUE" 格式
}
// 展开
expanded := os.ExpandEnv("$HOME/.config/$APP")
进程与信号
// 获取进程信息
fmt.Println(os.Getpid()) // 当前进程 PID
fmt.Println(os.Getppid()) // 父进程 PID
fmt.Println(os.Getwd()) // 当前工作目录
// 退出
os.Exit(1) // 立即退出(不执行 defer)
// 优雅退出
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-sigCh
log.Printf("received signal: %v", sig)
// 清理逻辑
os.Exit(0)
}()
bufio 包
Scanner(逐行/逐词读取)
// 逐行读取
f, _ := os.Open("large.log")
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
fmt.Println(line)
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
// 增大 buffer(默认 64KB,大行需要调大)
scanner := bufio.NewScanner(f)
scanner.Buffer(make([]byte, 0, 1024*1024), 1024*1024) // 1MB buffer
// 逐词读取
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
word := scanner.Text()
}
// 按字节扫描
scanner.Split(bufio.ScanBytes)
// 逐行读取带行号
scanner := bufio.NewScanner(os.Stdin)
lineNum := 0
for scanner.Scan() {
lineNum++
fmt.Printf("%d: %s\n", lineNum, scanner.Text())
}
Reader / Writer
// bufio.Reader:带缓冲读取
r := bufio.NewReader(f)
line, err := r.ReadString('\n') // 读到换行符
line = strings.TrimRight(line, "\n")
byte, err := r.ReadByte() // 读一个字节
r.UnreadByte() // 回退一个字节
// Peek:预读但不消费
peeked, err := r.Peek(4) // 预读 4 字节,不移动读指针
if string(peeked) == "\x89PNG" {
// 是 PNG 文件
}
// bufio.Writer:带缓冲写入
w := bufio.NewWriter(f)
w.WriteString("hello")
w.WriteByte('\n')
w.Flush() // 必须刷新!否则数据在缓冲区中没写入
defer w.Flush() // 或在 defer 中刷新
path/filepath 包
import "path/filepath"
// 路径拼接(跨平台安全)
p := filepath.Join("home", "user", "config.yaml")
// Linux: "home/user/config.yaml"
// Windows: "home\\user\\config.yaml"
// 路径清理
filepath.Clean("./a/b/../c/./d") // "a/c/d"
// 获取目录和文件名
filepath.Dir("/home/user/file.txt") // "/home/user"
filepath.Base("/home/user/file.txt") // "file.txt"
filepath.Ext("/home/user/file.txt") // ".txt"
// 分割路径和扩展名
dir, file := filepath.Split("/home/user/file.txt")
// dir = "/home/user/", file = "file.txt"
ext := filepath.Ext("file.tar.gz") // ".gz"
// 绝对路径
abs, _ := filepath.Abs("config.yaml")
abs, _ := filepath.Rel("/home/user", "/home/user/sub/file")
// abs = "sub/file"
// 通配符匹配
matches, _ := filepath.Glob("/data/*.log")
matches, _ := filepath.Glob("/data/**/*.yaml") // 不支持 **
// 遍历目录
filepath.WalkDir("/data", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil // 跳过目录
}
if filepath.Ext(path) == ".yaml" {
fmt.Println(path)
}
return nil
})
// 文件路径匹配(shell 模式)
matched, _ := filepath.Match("*.go", "main.go") // true
常用文件操作模式
安全写入(先写临时文件再 rename)
func SafeWriteFile(path string, data []byte, perm os.FileMode) error {
dir := filepath.Dir(path)
// 创建临时文件
tmp, err := os.CreateTemp(dir, ".tmp-*")
if err != nil {
return err
}
tmpPath := tmp.Name()
// 写入失败时清理临时文件
defer func() {
if err != nil {
os.Remove(tmpPath)
}
}()
// 写入数据
if _, err = tmp.Write(data); err != nil {
tmp.Close()
return err
}
// 确保数据刷盘
if err = tmp.Sync(); err != nil {
tmp.Close()
return err
}
tmp.Close()
// 原子性 rename
return os.Rename(tmpPath, path)
}
文件监听(简单轮询)
func WatchFile(path string, interval time.Duration, onChange func()) {
var lastMod time.Time
ticker := time.NewTicker(interval)
defer ticker.Stop()
for range ticker.C {
info, err := os.Stat(path)
if err != nil {
continue
}
if info.ModTime().After(lastMod) {
lastMod = info.ModTime()
onChange()
}
}
}
行尾处理平台差异
// Windows: \r\n, Unix: \n, Old Mac: \r
// bufio.Scanner 自动处理换行符差异
// 手动读取时需要处理:
func normalizeLineEndings(data []byte) []byte {
// 统一替换为 \n
data = bytes.ReplaceAll(data, []byte("\r\n"), []byte("\n"))
data = bytes.ReplaceAll(data, []byte("\r"), []byte("\n"))
return data
}
文件权限速查
| 权限 | 数值 | 说明 |
|---|
rwxrwxrwx | 0777 | 所有用户读写执行 |
rw-rw-rw- | 0666 | 所有用户读写 |
rwxr-xr-x | 0755 | 所有者读写执行,其他读执行 |
rw-r--r-- | 0644 | 所有者读写,其他只读 |
rwx------ | 0700 | 仅所有者读写执行 |
rw------- | 0600 | 仅所有者读写 |
首位 = 文件类型(- 普通文件, d 目录, l 符号链接)
后9位 = 3组 rwx(所有者/组/其他)
r = 4, w = 2, x = 1
0755 = rwx(7) r-x(5) r-x(5)
0644 = rw-(6) r--(4) r--(4)