Skip to content

文件系统

文件系统操作是 Go 程序中常见任务之一, 理解这些包和模式可以帮助你编写更健壮、高效的文件处理代码 Go语言提供了丰富的包用于文件系统操作, 以下是常用包及其功能

常用包与功能

包名主要功能
os操作系统交互、文件创建/打开/删除等基础操作
ioI/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--

注意事项

  1. 错误处理:文件操作几乎总是需要检查错误,Go 中没有异常机制
  2. 资源管理:打开的文件必须关闭,避免文件描述符泄露
  3. 路径分隔符:跨平台开发时,使用 filepath 包而非硬编码路径分隔符
  4. 性能考量
    • 频繁的小读写操作使用 bufio 包提高效率
    • 大文件操作考虑使用内存映射文件(mmap)或分块处理
  5. 安全考虑
    • 避免直接使用用户输入作为文件路径,防止路径遍历攻击
    • 在读取敏感文件时考虑使用最小权限原则
  6. 文件锁定: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
}

Released under the MIT License.