文件系统
文件系统操作是 Go 程序中常见任务之一, 理解这些包和模式可以帮助你编写更健壮、高效的文件处理代码 Go语言提供了丰富的包用于文件系统操作, 以下是常用包及其功能
常用包与功能
| 包名 | 主要功能 |
|---|---|
| os | 操作系统交互、文件创建/打开/删除等基础操作 |
| io | I/O 接口和基本实现 |
| io / fs | 文件系统抽象层, 提供通用文件系统操作接口 |
| path | 路径操作(跨平台) |
| bytes | 字节切片操作, 内存中模拟文件操作 |
| bufio | 带缓冲的 I/O 操作 |
| archive/tar | 处理 tar 格式文件的压缩包, 如 .tar.gz |
| archive/zip | 处理 zip 格式文件的压缩包, 如 .zip |
常用函数列表
os 包
| 函数 | 描述 |
|---|---|
| os.Create | 创建文件 |
| os.Open | 打开文件用于读取 |
| os.OpenFile | 按指定模式打开文件 |
| os.Stat | 获取文件信息 |
| os.Remove | 删除文件 |
| os.RemoveAll | 递归删除目录 |
| os.Mkdir | 创建目录 |
| os.MkdirAll | 递归创建目录 |
| os.Rename | 重命名/移动文件或目录 |
| os.Chmod | 修改文件权限 |
| os.Getenv | 获取环境变量 |
| os.UserHomeDir | 获取用户的家目录 ~ |
| os.UserConfigDir | 获取用户家目录下的 .config 配置目录 |
| os.UserCacheDir | 获取用户家目录下的 .cache 缓存目录 |
path/filepath 包
| 函数 | 描述 |
|---|---|
| filepath.Join | 跨平台拼接路径 |
| filepath.Abs | 获取绝对路径 |
| filepath.Dir | 获取目录路径部分 |
| filepath.Base | 获取文件名部分 |
| filepath.Ext | 获取文件扩展名 |
| filepath.Clean | 规范化路径 |
| filepath.Walk | 递归遍历目录树 |
| filepath.WalkDir | 高效递归遍历目录树(Go 1.16+) |
bufio 包
| 函数 | 描述 |
|---|---|
| bufio.NewReader | 创建带缓冲的读取器 |
| bufio.NewWriter | 创建带缓冲的写入器 |
| bufio.Scanner | 逐行扫描文件内容 |
| bufio.ReadBytes | 读取直到指定分隔符 |
| bufio.ReadString | 读取字符串直到指定分隔符 |
| bufio.NewScanner | 创建新的Scanner |
| bufio.Writer.Flush | 刷新缓冲区到写入器 |
bytes 包
| 函数 | 描述 |
|---|---|
| bytes.Buffer | 内存缓冲区,实现 io.Reader 和 io.Writer |
| bytes.NewReader | 从 []byte 创建 io.Reader |
| bytes.Split | 拆分字节切片 |
| bytes.Contains | 检查字节切片是否包含子切片 |
| bytes.TrimSpace | 去除字节切片两端的空白字符 |
| bytes.Equal | 比较两个字节切片是否相等 |
| bytes.NewReader | 从字节切片创建只读的 io.Reader |
ioutil (已弃用,功能分散到其他包)
注意:在 Go 1.16+ 中,ioutil 包已被弃用,其功能分散到 io 和 os 包中
代码示例
go
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
)
func main() {
// 1. 文件基本操作
// 创建文件
file, err := os.Create("example.txt")
if err != nil {
fmt.Println("创建文件失败:", err)
return
}
defer file.Close() // 确保文件关闭
// 写入内容
content := "Hello, Go file system!\nThis is a test file."
_, err = file.WriteString(content)
if err != nil {
fmt.Println("写入文件失败:", err)
return
}
fmt.Println("文件创建并写入成功")
// 2. 读取文件内容
// 方法一:一次性读取
data, err := os.ReadFile("example.txt")
if err != nil {
fmt.Println("读取文件失败:", err)
return
}
fmt.Printf("读取的文件内容: %s\n", string(data))
// 方法二:使用缓冲区逐行读取
file, err = os.Open("example.txt")
if err != nil {
fmt.Println("打开文件失败:", err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
fmt.Println("逐行读取内容:")
lineNum := 1
for scanner.Scan() {
fmt.Printf("行 %d: %s\n", lineNum, scanner.Text())
lineNum++
}
if err := scanner.Err(); err != nil {
fmt.Println("读取文件出错:", err)
}
// 3. 路径操作
currentDir, _ := os.Getwd()
filePath := filepath.Join(currentDir, "example.txt")
absPath, _ := filepath.Abs(filePath)
fmt.Printf("当前工作目录: %s\n", currentDir)
fmt.Printf("文件绝对路径: %s\n", absPath)
fmt.Printf("文件所在目录: %s\n", filepath.Dir(absPath))
fmt.Printf("文件名: %s\n", filepath.Base(absPath))
fmt.Printf("文件扩展名: %s\n", filepath.Ext(absPath))
// 4. 目录操作
dirPath := filepath.Join(currentDir, "testdir")
err = os.MkdirAll(dirPath, 0755) // 递归创建目录
if err != nil {
fmt.Println("创建目录失败:", err)
} else {
fmt.Printf("目录创建成功: %s\n", dirPath)
}
// 5. 遍历目录
fmt.Println("\n遍历当前目录:")
err = filepath.Walk(currentDir, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
// 只显示文件名,而非完整路径
relPath, _ := filepath.Rel(currentDir, path)
if info.IsDir() {
fmt.Printf("[DIR] %s\n", relPath)
} else {
fmt.Printf("[FILE] %s (大小: %d 字节)\n", relPath, info.Size())
}
return nil
})
if err != nil {
fmt.Println("遍历目录失败:", err)
}
// 6. 使用 bytes.Buffer 模拟文件操作
var buffer bytes.Buffer
buffer.WriteString("这是内存中的缓冲区\n")
buffer.WriteString("可以像文件一样操作")
// 从缓冲区读取
reader := bufio.NewReader(&buffer)
fmt.Println("\n从内存缓冲区读取:")
for {
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
fmt.Print(line) // 打印最后一行
break
}
fmt.Println("读取缓冲区出错:", err)
break
}
fmt.Print(line)
}
// 7. 文件复制
sourceFile := "example.txt"
destFile := filepath.Join(dirPath, "copy.txt")
err = copyFile(sourceFile, destFile)
if err != nil {
fmt.Println("文件复制失败:", err)
} else {
fmt.Printf("文件已成功复制到 %s\n", destFile)
}
// 8. 清理创建的文件和目录
defer os.Remove("example.txt")
defer os.RemoveAll(dirPath)
fmt.Println("\n示例程序结束,已创建的临时文件和目录将在程序退出时清理")
}
// 文件复制函数
func copyFile(src, dst string) error {
sourceFile, err := os.Open(src)
if err != nil {
return err
}
defer sourceFile.Close()
destFile, err := os.Create(dst)
if err != nil {
return err
}
defer destFile.Close()
// 使用缓冲区提高复制效率
buffer := make([]byte, 1024)
_, err = io.CopyBuffer(destFile, sourceFile, buffer)
return err
}实用技巧
1. 文件操作最佳实践
- 始终使用
defer file.Close()确保文件正确关闭 - 处理大文件时使用缓冲区(bufio)或分块读取,避免内存溢出
- 文件路径使用
filepath.Join()而非字符串拼接,保证跨平台兼容性 - 使用
os.IsNotExist(err)检查文件是否存在
2. 高效读取大文件
go
// 使用 bufio.Scanner 逐行读取大文件
file, _ := os.Open("largefile.log")
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
// 处理每一行
}3. 内存文件操作
go
// 使用 bytes.Buffer 模拟文件操作,无需真实I/O
var buffer bytes.Buffer
buffer.WriteString("内存中的文本")
data := buffer.Bytes() // 获取字节切片4. 临时文件处理
go
// 创建临时文件
tempFile, err := os.CreateTemp("", "example-*.txt")
if err != nil {
// 处理错误
}
defer os.Remove(tempFile.Name()) // 确保清理临时文件5. 权限设置
go
// 设置文件权限 (Unix-like 系统)
err := os.Chmod("file.txt", 0644) // rw-r--r--注意事项
- 错误处理:文件操作几乎总是需要检查错误,Go 中没有异常机制
- 资源管理:打开的文件必须关闭,避免文件描述符泄露
- 路径分隔符:跨平台开发时,使用
filepath包而非硬编码路径分隔符 - 性能考量:
- 频繁的小读写操作使用
bufio包提高效率 - 大文件操作考虑使用内存映射文件(mmap)或分块处理
- 频繁的小读写操作使用
- 安全考虑:
- 避免直接使用用户输入作为文件路径,防止路径遍历攻击
- 在读取敏感文件时考虑使用最小权限原则
- 文件锁定:Go 标准库不提供跨平台文件锁,考虑使用第三方包如
github.com/gofrs/flock
常见模式
1. 原子写入文件(避免部分写入)
go
func writeFileAtomically(filename, content string) error {
// 1. 写入临时文件
tmpFile, err := os.CreateTemp(filepath.Dir(filename), filepath.Base(filename)+".*.tmp")
if err != nil {
return err
}
defer os.Remove(tmpFile.Name()) // 确保清理
if _, err := tmpFile.WriteString(content); err != nil {
tmpFile.Close()
return err
}
if err := tmpFile.Close(); err != nil {
return err
}
// 2. 重命名临时文件(原子操作)
return os.Rename(tmpFile.Name(), filename)
}2. 递归创建目录
go
// 确保目录存在
func ensureDir(path string) error {
// 检查是否已存在
info, err := os.Stat(path)
if err == nil {
if info.IsDir() {
return nil // 目录已存在
}
return fmt.Errorf("%s exists but is not a directory", path)
}
// 创建目录(包括所有父目录)
return os.MkdirAll(path, 0755)
}3. 高效文件复制
go
func copyFile(src, dst string) error {
sourceFile, err := os.Open(src)
if err != nil {
return err
}
defer sourceFile.Close()
destFile, err := os.Create(dst)
if err != nil {
return err
}
defer destFile.Close()
// 使用 io.Copy 内部有优化
_, err = io.Copy(destFile, sourceFile)
return err
}