Go 文件操作
文件操作是编程中最常见的需求之一。Go 标准库提供了丰富的文件 I/O 功能,包括文件读写、目录操作、路径处理等。通过 os、io、bufio 和 path/filepath 等包的配合,可以高效地处理各种文件系统操作。
os 包基础
os 包提供了与操作系统交互的函数,包括文件的打开、创建、读取和写入。
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 操作都围绕它们展开。
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 效率。
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() 确保数据写出。
文件读写完整示例
下面是一个完整的文件复制程序,展示了文件操作的最佳实践。
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 包提供了目录的创建、遍历和删除功能。
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 包提供了跨平台的路径操作函数。
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 提供了创建临时文件和目录的便捷函数。
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 确保文件在函数返回时被关闭。
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() 确保文件资源正确释放。掌握这些基础操作,可以应对绝大多数文件系统相关的需求。