4.6 文件与 IO 操作
os 包文件系统操作
文件基本操作
go
package main
import (
"fmt"
"os"
)
func main() {
// 创建文件
file, err := os.Create("test.txt")
if err != nil {
fmt.Println("创建失败:", err)
return
}
defer file.Close()
// 写入数据
file.WriteString("Hello, World!\n")
file.Write([]byte("更多数据\n"))
// 打开文件读取
file2, err := os.Open("test.txt")
if err != nil {
fmt.Println("打开失败:", err)
return
}
defer file2.Close()
// 读取内容
buf := make([]byte, 1024)
n, _ := file2.Read(buf)
fmt.Println("内容:", string(buf[:n]))
// 追加模式打开
file3, err := os.OpenFile("test.txt", os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
fmt.Println("打开失败:", err)
return
}
defer file3.Close()
file3.WriteString("追加的内容\n")
// 删除文件
// os.Remove("test.txt")
// 重命名文件
// os.Rename("old.txt", "new.txt")
}文件信息操作
go
package main
import (
"fmt"
"os"
"time"
)
func main() {
// 获取文件信息
info, err := os.Stat("test.txt")
if err != nil {
fmt.Println("错误:", err)
return
}
fmt.Println("文件名:", info.Name())
fmt.Println("大小:", info.Size(), "bytes")
fmt.Println("修改时间:", info.ModTime())
fmt.Println("是否目录:", info.IsDir())
fmt.Println("权限:", info.Mode())
// 检查文件是否存在
if _, err := os.Stat("test.txt"); os.IsNotExist(err) {
fmt.Println("文件不存在")
} else {
fmt.Println("文件存在")
}
// 修改文件权限
os.Chmod("test.txt", 0755)
// 修改修改时间
now := time.Now()
os.Chtimes("test.txt", now, now)
// 创建目录
os.Mkdir("testdir", 0755)
os.MkdirAll("a/b/c", 0755) // 递归创建
// 删除目录
os.Remove("testdir")
os.RemoveAll("a") // 递归删除
}目录操作
go
package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
// 获取当前目录
wd, _ := os.Getwd()
fmt.Println("当前目录:", wd)
// 切换目录
os.Chdir("/tmp")
// 读取目录内容
entries, _ := os.ReadDir(".")
for _, entry := range entries {
fmt.Println(entry.Name(), entry.IsDir())
}
// 读取目录内容 (详细)
infos, _ := os.ReadDir(".")
for _, info := range infos {
fmt.Println(info.Name())
}
// 遍历目录树
filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
fmt.Printf("路径:%s, 类型:%s, 大小:%d\n",
path,
map[bool]string{true: "目录", false: "文件"}[info.IsDir()],
info.Size())
return nil
})
// Go 1.16+ 使用 WalkDir
filepath.WalkDir(".", func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
fmt.Printf("路径:%s, 目录:%v\n", path, d.IsDir())
return nil
})
}文件权限与模式
go
package main
import (
"fmt"
"os"
)
func main() {
// 文件模式常量
// os.ModePerm = 0777 // 文件权限位
// os.ModeDir = 1 << 32 - 1 << iota
// os.ModeAppend // 只能追加
// os.ModeExclusive // 只能独占使用
// os.ModeTemporary // 临时文件
// os.ModeSymlink // 符号链接
// os.ModeDevice // 设备文件
// os.ModeSocket // socket 文件
// 创建带权限的文件
file, _ := os.OpenFile("test.txt", os.O_CREATE|os.O_WRONLY, 0644)
file.Close()
// 修改权限
os.Chmod("test.txt", 0755)
// 检查权限
info, _ := os.Stat("test.txt")
mode := info.Mode()
// 检查是否是目录
if mode.IsDir() {
fmt.Println("是目录")
}
// 检查权限位
if mode&0200 != 0 {
fmt.Println("所有者可写")
}
}io.Reader 与 io.Writer
基础接口
go
package main
import (
"io"
"os"
"strings"
)
// io.Reader 接口
// type Reader interface {
// Read(p []byte) (n int, err error)
// }
// io.Writer 接口
// type Writer interface {
// Write(p []byte) (n int, err error)
// }
// io.ReadWriter = Reader + Writer
// io.ReadWriteCloser = Reader + Writer + Closer
func main() {
// 从字符串读取
reader := strings.NewReader("Hello, World!")
buf := make([]byte, 5)
n, _ := reader.Read(buf)
println(string(buf[:n])) // Hello
// 写入文件
file, _ := os.Create("output.txt")
defer file.Close()
writer := io.Writer(file)
writer.Write([]byte("test"))
}io.Copy 与 io.Pipe
go
package main
import (
"io"
"os"
"strings"
)
func main() {
// io.Copy: 从 reader 复制到 writer
src := strings.NewReader("Hello, World!")
dst, _ := os.Create("output.txt")
defer dst.Close()
copied, _ := io.Copy(dst, src)
println("复制了", copied, "字节")
// io.CopyN: 复制指定字节数
src2 := strings.NewReader("Hello, World!")
dst2, _ := os.Create("output2.txt")
defer dst2.Close()
io.CopyN(dst2, src2, 5) // 只复制 5 字节
// io.Pipe: 创建管道 (同步的 in-memory pipe)
reader, writer := io.Pipe()
go func() {
writer.Write([]byte("hello"))
writer.Close()
}()
buf := make([]byte, 1024)
n, _ := reader.Read(buf)
println(string(buf[:n]))
// io.MultiReader: 合并多个 reader
mr := io.MultiReader(
strings.NewReader("Hello, "),
strings.NewReader("World!"),
)
io.Copy(os.Stdout, mr) // 输出:Hello, World!
// io.MultiWriter: 写入多个 destination
mw := io.MultiWriter(os.Stdout, dst)
mw.Write([]byte("test")) // 同时输出到 stdout 和文件
}自定义 Reader/Writer
go
package main
import (
"errors"
"io"
)
// 自定义 Reader
type CountingReader struct {
r io.Reader
count int64
}
func (cr *CountingReader) Read(p []byte) (int, error) {
n, err := cr.r.Read(p)
cr.count += int64(n)
return n, err
}
func (cr *CountingReader) Count() int64 {
return cr.count
}
// 自定义 Writer
type LoggingWriter struct {
w io.Writer
}
func (lw *LoggingWriter) Write(p []byte) (int, error) {
println("写入:", len(p), "字节")
return lw.w.Write(p)
}
// 实现 io.ReaderFrom / io.WriterTo 优化
type FastCopier struct{}
// WriterTo 接口可以让 io.Copy 使用更高效的方式
func (fc *FastCopier) WriteTo(w io.Writer) (int64, error) {
// 直接写入,避免中间 buffer
data := []byte("fast copy")
return int64(len(data)), w.Write(data)
}bufio 带缓冲的 IO
bufio.Reader
go
package main
import (
"bufio"
"fmt"
"strings"
)
func main() {
// 创建带缓冲的 reader
reader := bufio.NewReader(strings.NewReader("line1\nline2\nline3\n"))
// 读取一行
line, _ := reader.ReadString('\n')
fmt.Println("第一行:", line)
// 读取到分隔符 (返回 []byte)
line2, _ := reader.ReadBytes('\n')
fmt.Println("第二行:", string(line2))
// 读取 rune
r, _, _ := reader.ReadRune()
fmt.Println("第一个字符:", string(r))
// Peek: 查看接下来的 n 个字节 (不消耗)
peeked, _ := reader.Peek(5)
fmt.Println("Peek:", string(peeked))
// Buffered: 返回缓冲中可用的字节数
fmt.Println("缓冲中:", reader.Buffered(), "字节")
// Discard: 跳过 n 个字节
reader.Discard(2)
}bufio.Writer
go
package main
import (
"bufio"
"os"
)
func main() {
file, _ := os.Create("output.txt")
defer file.Close()
// 创建带缓冲的 writer
writer := bufio.NewWriter(file)
// 写入
writer.WriteString("Hello, ")
writer.WriteByte('W')
writer.WriteString("orld!\n")
// 刷新缓冲区
writer.Flush()
// 可用空间
avail := writer.Available()
println("可用空间:", avail)
// 已缓冲但未刷新的字节数
buffered := writer.Buffered()
println("已缓冲:", buffered)
}bufio.Scanner
go
package main
import (
"bufio"
"fmt"
"strings"
)
func main() {
// 按行扫描
scanner := bufio.NewScanner(strings.NewReader("line1\nline2\nline3"))
for scanner.Scan() {
line := scanner.Text()
fmt.Println("行:", line)
}
// 检查错误
if err := scanner.Err(); err != nil {
fmt.Println("错误:", err)
}
// 自定义分割函数 (按单词扫描)
scanner2 := bufio.NewScanner(strings.NewReader("hello world foo bar"))
scanner2.Split(bufio.ScanWords)
for scanner2.Scan() {
fmt.Println("单词:", scanner2.Text())
}
// 内置分割函数
// bufio.ScanLines - 按行
// bufio.ScanWords - 按单词
// bufio.ScanRunes - 按 rune
// bufio.ScanBytes - 按字节
// 自定义分割函数
data := "a,b,c,d"
scanner3 := bufio.NewScanner(strings.NewReader(data))
// 按逗号分割
scanner3.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
for i := 0; i < len(data); i++ {
if data[i] == ',' {
return i + 1, data[:i], nil
}
}
if !atEOF {
return 0, nil, nil
}
return len(data), data, nil
})
for scanner3.Scan() {
fmt.Println("分割:", scanner3.Text())
}
}ioutil 便捷函数
读取操作
go
package main
import (
"fmt"
"io/ioutil"
"os"
)
func main() {
// 读取整个文件
data, err := ioutil.ReadFile("test.txt")
if err != nil {
fmt.Println("错误:", err)
return
}
fmt.Println("内容:", string(data))
// 读取目录条目
entries, err := ioutil.ReadDir(".")
if err != nil {
fmt.Println("错误:", err)
return
}
for _, entry := range entries {
fmt.Println(entry.Name(), entry.Size())
}
// Go 1.16+ 推荐使用 os 包替代
data2, _ := os.ReadFile("test.txt") // 替代 ioutil.ReadFile
entries2, _ := os.ReadDir(".") // 替代 ioutil.ReadDir
}写入操作
go
package main
import (
"io/ioutil"
"os"
)
func main() {
// 写入整个文件 (覆盖)
ioutil.WriteFile("output.txt", []byte("Hello"), 0644)
// Go 1.16+ 推荐使用 os 包替代
os.WriteFile("output.txt", []byte("Hello"), 0644)
// 创建临时文件
tmpFile, err := ioutil.TempFile("", "prefix-")
if err != nil {
panic(err)
}
defer os.Remove(tmpFile.Name()) // 清理
defer tmpFile.Close()
tmpFile.WriteString("临时数据")
// 创建临时目录
tmpDir, err := ioutil.TempDir("", "prefix-")
if err != nil {
panic(err)
}
defer os.RemoveAll(tmpDir) // 清理
}路径处理 (filepath)
路径操作
go
package main
import (
"fmt"
"path/filepath"
"runtime"
)
func main() {
// 路径分隔符
fmt.Println("分隔符:", filepath.Separator)
fmt.Println("系统:", runtime.GOOS)
// 连接路径
path := filepath.Join("home", "user", "docs")
fmt.Println("连接:", path)
// 清理路径
clean := filepath.Clean("/home/../home/./user")
fmt.Println("清理:", clean)
// 绝对路径
abs, _ := filepath.Abs("relative/path")
fmt.Println("绝对:", abs)
// 规范路径 (清理 + 绝对路径)
norm := filepath.Clean("/home/../home/user")
fmt.Println("规范:", norm)
// 路径分隔
dir := filepath.Dir("/home/user/file.txt")
fmt.Println("目录:", dir)
base := filepath.Base("/home/user/file.txt")
fmt.Println("文件名:", base)
ext := filepath.Ext("/home/user/file.txt")
fmt.Println("扩展名:", ext)
// 分割扩展名
name, ext2 := filepath.Split("file.txt")
fmt.Println("名字:", name, "扩展:", ext2)
}Glob 模式匹配
go
package main
import (
"fmt"
"path/filepath"
)
func main() {
// Glob: 查找匹配模式的文件
matches, _ := filepath.Glob("*.go")
fmt.Println("Go 文件:", matches)
matches2, _ := filepath.Glob("*.md")
fmt.Println("MD 文件:", matches2)
// 匹配模式:
// * - 匹配任意非分隔符字符
// ? - 匹配单个非分隔符字符
// [abc] - 匹配 a, b, 或 c
// [^abc]- 匹配非 a, b, c 的字符
}路径转换
go
package main
import (
"fmt"
"path/filepath"
)
func main() {
// 转为相对路径
rel, _ := filepath.Rel("/home/user", "/home/user/docs/file.txt")
fmt.Println("相对:", rel)
// 转为绝对路径
abs, _ := filepath.Abs("docs/file.txt")
fmt.Println("绝对:", abs)
// 卷名 (Windows)
vol := filepath.VolumeName("C:\\Users\\file.txt")
fmt.Println("卷名:", vol)
// 匹配
matched, _ := filepath.Match("*.txt", "file.txt")
fmt.Println("匹配:", matched)
}实用文件操作函数
文件复制
go
package main
import (
"io"
"os"
"path/filepath"
)
// 复制文件
func CopyFile(src, dst string) error {
source, err := os.Open(src)
if err != nil {
return err
}
defer source.Close()
dest, err := os.Create(dst)
if err != nil {
return err
}
defer dest.Close()
_, err = io.Copy(dest, source)
return err
}
// 复制目录
func CopyDir(src, dst string) error {
entries, err := os.ReadDir(src)
if err != nil {
return err
}
os.MkdirAll(dst, 0755)
for _, entry := range entries {
srcPath := filepath.Join(src, entry.Name())
dstPath := filepath.Join(dst, entry.Name())
if entry.IsDir() {
if err := CopyDir(srcPath, dstPath); err != nil {
return err
}
} else {
if err := CopyFile(srcPath, dstPath); err != nil {
return err
}
}
}
return nil
}文件查找
go
package main
import (
"os"
"path/filepath"
"strings"
)
// 查找指定扩展名的文件
func FindFiles(dir, ext string) ([]string, error) {
var files []string
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && strings.HasSuffix(info.Name(), ext) {
files = append(files, path)
}
return nil
})
return files, err
}
// 查找包含关键字的文件
func FindFilesWithContent(dir, keyword string) ([]string, error) {
var result []string
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil || info.IsDir() {
return err
}
data, _ := os.ReadFile(path)
if strings.Contains(string(data), keyword) {
result = append(result, path)
}
return nil
})
return result, nil
}文件大小计算
go
package main
import (
"os"
"path/filepath"
)
// 计算目录大小
func DirSize(path string) (int64, error) {
var size int64
err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
size += info.Size()
}
return nil
})
return size, err
}
// 格式化文件大小
func FormatFileSize(size int64) string {
const (
KB = 1024
MB = KB * 1024
GB = MB * 1024
)
switch {
case size >= GB:
return fmt.Sprintf("%.2f GB", float64(size)/GB)
case size >= MB:
return fmt.Sprintf("%.2f MB", float64(size)/MB)
case size >= KB:
return fmt.Sprintf("%.2f KB", float64(size)/KB)
default:
return fmt.Sprintf("%d B", size)
}
}文件 IO 检查清单
[ ] 使用 defer 关闭文件
[ ] 检查所有 IO 错误
[ ] 大文件使用 bufio 缓冲读取
[ ] 使用 io.Copy 代替手动复制
[ ] 路径使用 filepath 包处理 (跨平台)
[ ] 临时文件使用后清理
[ ] 目录遍历处理错误情况
[ ] 文件权限设置合理
[ ] 使用 ReadFile/WriteFile 简化小文件操作
[ ] 大量小文件考虑使用 tar/zip 打包
[ ] 敏感文件设置合适的权限
[ ] 文件锁避免并发问题