# Go 文件操作

文件操作是编程中最常见的需求之一。Go 标准库提供了丰富的文件 I/O 功能，包括文件读写、目录操作、路径处理等。通过 `os`、`io`、`bufio` 和 `path/filepath` 等包的配合，可以高效地处理各种文件系统操作。

## os 包基础

`os` 包提供了与操作系统交互的函数，包括文件的打开、创建、读取和写入。

```go
package main

import (
	"fmt"
	"os"
)

func main() {
	// 写入文件（一次性写入）
	content := []byte("Hello, Go 文件操作!\n第二行内容\n")
	err := os.WriteFile("/tmp/go-demo/hello.txt", content, 0644)
	if err != nil {
		fmt.Println("写入失败:", err)
		return
	}
	fmt.Println("写入成功")

	// 读取文件（一次性读取）
	data, err := os.ReadFile("/tmp/go-demo/hello.txt")
	if err != nil {
		fmt.Println("读取失败:", err)
		return
	}
	fmt.Printf("读取内容:\n%s\n", string(data))

	// 打开文件进行更细粒度的操作
	file, err := os.Open("/tmp/go-demo/hello.txt")
	if err != nil {
		fmt.Println("打开失败:", err)
		return
	}
	defer file.Close() // 确保文件被关闭

	// 读取前 5 个字节
	buf := make([]byte, 5)
	n, err := file.Read(buf)
	fmt.Printf("读取了 %d 字节: %s\n", n, string(buf[:n]))

	// 获取文件信息
	info, err := file.Stat()
	if err != nil {
		fmt.Println("获取信息失败:", err)
		return
	}
	fmt.Printf("文件名: %s, 大小: %d 字节\n", info.Name(), info.Size())
}
```

`os.WriteFile` 和 `os.ReadFile` 适合小文件的读写。对于大文件或需要精细控制的场景，使用 `os.Open` 配合 `Read`/`Write` 方法。

## io.Reader 和 io.Writer 接口

`io.Reader` 和 `io.Writer` 是 Go I/O 的两个核心接口，几乎所有 I/O 操作都围绕它们展开。

```go
package main

import (
	"fmt"
	"io"
	"os"
	"strings"
)

func main() {
	// strings.Reader 实现了 io.Reader
	reader := strings.NewReader("Hello, io.Reader!")

	// 从 reader 读取到 buffer
	buf := make([]byte, 5)
	for {
		n, err := reader.Read(buf)
		if n > 0 {
			fmt.Printf("读取: %s\n", string(buf[:n]))
		}
		if err == io.EOF {
			fmt.Println("读取完毕")
			break
		}
		if err != nil {
			fmt.Println("读取错误:", err)
			break
		}
	}

	// os.Stdout 实现了 io.Writer
	writer := io.Writer(os.Stdout)
	writer.Write([]byte("写入到标准输出\n"))

	// io.Copy 从一个 Reader 复制到 Writer
	src := strings.NewReader("通过 io.Copy 复制的内容\n")
	io.Copy(os.Stdout, src)
}
```

`io.Reader` 接口只有一个方法：`Read(p []byte) (n int, err error)`。`io.Writer` 接口也只有一个方法：`Write(p []byte) (n int, err error)`。理解这两个接口是掌握 Go I/O 的关键。

## bufio 缓冲读写

`bufio` 包提供了带缓冲的读写器，减少系统调用次数，提高 I/O 效率。

```go
package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"
)

func main() {
	// === bufio.Scanner 按行读取 ===
	text := "第一行\n第二行\n第三行\n第四行"
	scanner := bufio.NewScanner(strings.NewReader(text))

	lineNum := 0
	for scanner.Scan() {
		lineNum++
		fmt.Printf("第 %d 行: %s\n", lineNum, scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		fmt.Println("扫描错误:", err)
	}

	// === bufio.Reader 读取文件 ===
	// 先创建一个示例文件
	os.WriteFile("/tmp/go-demo/bufio.txt", []byte("line1\nline2\nline3\n"), 0644)

	file, err := os.Open("/tmp/go-demo/bufio.txt")
	if err != nil {
		fmt.Println("打开失败:", err)
		return
	}
	defer file.Close()

	reader := bufio.NewReader(file)
	for {
		line, err := reader.ReadString('\n')
		fmt.Printf("读取: %s", line)
		if err != nil {
			break
		}
	}

	// === bufio.Writer 缓冲写入 ===
	var buf strings.Builder
	writer := bufio.NewWriter(&buf)

	writer.WriteString("缓冲写入第一行\n")
	writer.WriteString("缓冲写入第二行\n")
	writer.Flush() // 必须 Flush 才能将缓冲内容写出

	fmt.Println("\n--- 缓冲写入结果 ---")
	fmt.Print(buf.String())
}
```

`bufio.Scanner` 适合按行或按自定义分隔符读取文本。`bufio.Reader` 提供更底层的缓冲读取能力。`bufio.Writer` 通过缓冲减少写操作次数，最后必须调用 `Flush()` 确保数据写出。

## 文件读写完整示例

下面是一个完整的文件复制程序，展示了文件操作的最佳实践。

```go
package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

// copyFile 使用 io.Copy 实现文件复制
func copyFile(src, dst string) error {
	sourceFile, err := os.Open(src)
	if err != nil {
		return fmt.Errorf("打开源文件失败: %w", err)
	}
	defer sourceFile.Close()

	destFile, err := os.Create(dst)
	if err != nil {
		return fmt.Errorf("创建目标文件失败: %w", err)
	}
	defer destFile.Close()

	n, err := io.Copy(destFile, sourceFile)
	if err != nil {
		return fmt.Errorf("复制失败: %w", err)
	}

	fmt.Printf("复制完成，共 %d 字节\n", n)
	return nil
}

// copyFileBuffered 使用 bufio 缓冲复制大文件
func copyFileBuffered(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()

	reader := bufio.NewReader(sourceFile)
	writer := bufio.NewWriter(destFile)

	if _, err := io.Copy(writer, reader); err != nil {
		return err
	}

	return writer.Flush() // 确保缓冲数据写入磁盘
}

func main() {
	// 创建示例文件
	os.MkdirAll("/tmp/go-demo", 0755)
	os.WriteFile("/tmp/go-demo/original.txt", []byte("这是原始文件的内容。\n包含多行文本。\n"), 0644)

	// 复制文件
	err := copyFile("/tmp/go-demo/original.txt", "/tmp/go-demo/copy.txt")
	if err != nil {
		fmt.Println("错误:", err)
		return
	}

	// 验证复制结果
	data, _ := os.ReadFile("/tmp/go-demo/copy.txt")
	fmt.Printf("复制的文件内容:\n%s", string(data))
}
```

## 目录操作

`os` 包提供了目录的创建、遍历和删除功能。

```go
package main

import (
	"fmt"
	"os"
	"path/filepath"
)

func main() {
	baseDir := "/tmp/go-demo/dirs"

	// 创建多级目录
	err := os.MkdirAll(baseDir+"/a/b/c", 0755)
	if err != nil {
		fmt.Println("创建目录失败:", err)
		return
	}
	fmt.Println("目录创建成功")

	// 在子目录中创建文件
	os.WriteFile(baseDir+"/a/file1.txt", []byte("file1"), 0644)
	os.WriteFile(baseDir+"/a/b/file2.txt", []byte("file2"), 0644)

	// 读取目录内容
	entries, err := os.ReadDir(baseDir + "/a")
	if err != nil {
		fmt.Println("读取目录失败:", err)
		return
	}

	fmt.Println("\n--- 目录内容 ---")
	for _, entry := range entries {
		info, _ := entry.Info()
		typeStr := "文件"
		if entry.IsDir() {
			typeStr = "目录"
		}
		fmt.Printf("  %s [%s] %d 字节\n", entry.Name(), typeStr, info.Size())
	}

	// 递归遍历目录树
	fmt.Println("\n--- 递归遍历 ---")
	filepath.Walk(baseDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		indent := ""
		for i := 0; i < len(path)-len(baseDir); i++ {
			if path[i] == '/' {
				indent += "  "
			}
		}
		fmt.Printf("%s%s\n", indent, info.Name())
		return nil
	})

	// 删除目录（递归删除）
	err = os.RemoveAll(baseDir)
	if err != nil {
		fmt.Println("删除失败:", err)
		return
	}
	fmt.Println("\n目录已删除")
}
```

注意上面的 `filepath.Walk` 需要导入 `path/filepath` 包。`os.ReadDir` 返回 `[]os.DirEntry`，比旧的 `ReadDir` 更高效。

## 路径操作

`path/filepath` 包提供了跨平台的路径操作函数。

```go
package main

import (
	"fmt"
	"path/filepath"
	"strings"
)

func main() {
	path := "/home/user/documents/report.pdf"

	fmt.Println("完整路径:", path)
	fmt.Println("目录部分:", filepath.Dir(path))     // /home/user/documents
	fmt.Println("文件名:", filepath.Base(path))       // report.pdf
	fmt.Println("扩展名:", filepath.Ext(path))        // .pdf
	fmt.Println("无扩展名:", strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)))

	// 路径拼接（自动处理分隔符）
	newPath := filepath.Join("/home", "user", "data", "file.txt")
	fmt.Println("\n拼接路径:", newPath)

	// 绝对路径
	abs, _ := filepath.Abs("relative/path/file.txt")
	fmt.Println("绝对路径:", abs)

	// 相对路径
	base := "/home/user"
	target := "/home/user/documents/file.txt"
	rel, _ := filepath.Rel(base, target)
	fmt.Println("相对路径:", rel) // documents/file.txt

	// 遍历匹配文件（Glob）
	matches, _ := filepath.Glob("/usr/local/go/bin/*")
	fmt.Println("\n/usr/local/go/bin/ 下的文件:")
	for _, m := range matches {
		fmt.Println(" ", filepath.Base(m))
	}
}
```

`filepath.Join` 是拼接路径的推荐方式，它会自动使用当前操作系统的路径分隔符。`filepath.Glob` 支持通配符匹配文件。

## 临时文件与目录

Go 提供了创建临时文件和目录的便捷函数。

```go
package main

import (
	"fmt"
	"os"
)

func main() {
	// 创建临时文件
	tmpFile, err := os.CreateTemp("", "example-*.txt")
	if err != nil {
		fmt.Println("创建临时文件失败:", err)
		return
	}
	defer os.Remove(tmpFile.Name()) // 程序结束时清理
	defer tmpFile.Close()

	fmt.Println("临时文件:", tmpFile.Name())

	// 写入临时文件
	tmpFile.WriteString("临时数据\n")

	// 创建临时目录
	tmpDir, err := os.MkdirTemp("", "example-*")
	if err != nil {
		fmt.Println("创建临时目录失败:", err)
		return
	}
	defer os.RemoveAll(tmpDir) // 程序结束时清理

	fmt.Println("临时目录:", tmpDir)

	// 在临时目录中创建文件
	fileInTmp, err := os.Create(tmpDir + "/data.txt")
	if err != nil {
		fmt.Println("创建文件失败:", err)
		return
	}
	fileInTmp.WriteString("临时目录中的文件")
	fileInTmp.Close()

	fmt.Println("临时文件操作完成")
}
```

`os.CreateTemp` 和 `os.MkdirTemp` 的第一个参数是目录（空字符串表示系统默认临时目录），第二个参数是文件名模式（`*` 会被替换为随机字符串）。

## defer 关闭文件的重要性

在 Go 中，打开文件后必须关闭。使用 `defer` 确保文件在函数返回时被关闭。

```go
package main

import (
	"fmt"
	"os"
)

// 错误示例：忘记关闭文件
func badExample() {
	file, _ := os.Open("somefile.txt")
	// 忘记调用 file.Close()
	// 文件描述符泄漏！
	_ = file
}

// 正确示例：使用 defer 关闭文件
func goodExample() {
	file, err := os.Open("somefile.txt")
	if err != nil {
		fmt.Println("打开失败:", err)
		return
	}
	defer file.Close() // 函数返回时自动关闭

	// 使用 file...
}

// 最佳实践：封装文件操作函数
func readFirstLine(filename string) (string, error) {
	file, err := os.Open(filename)
	if err != nil {
		return "", err
	}
	defer file.Close()

	buf := make([]byte, 1024)
	n, err := file.Read(buf)
	if err != nil && err.Error() != "EOF" {
		return "", err
	}
	return string(buf[:n]), nil
}

func main() {
	// 创建测试文件
	os.WriteFile("/tmp/go-demo/somefile.txt", []byte("第一行内容"), 0644)

	line, err := readFirstLine("/tmp/go-demo/somefile.txt")
	if err != nil {
		fmt.Println("读取失败:", err)
		return
	}
	fmt.Println("内容:", line)
}
```

`defer` 会在函数返回时（无论正常返回还是 panic）执行 `Close()`。这是 Go 中管理资源的标准模式，确保资源不会泄漏。

## 总结

Go 的文件操作围绕 `os`、`io`、`bufio` 和 `path/filepath` 四个核心包展开。`os.ReadFile`/`WriteFile` 适合小文件的一次性读写；`io.Reader`/`Writer` 是 I/O 的基础接口；`bufio` 提供缓冲读写以提升性能；`path/filepath` 处理跨平台路径操作。始终使用 `defer file.Close()` 确保文件资源正确释放。掌握这些基础操作，可以应对绝大多数文件系统相关的需求。

