Skip to content

Go 语言 os 包与文件操作教程

os 包提供了操作系统功能接口,包括文件系统、进程、环境变量等。本教程重点关注其在文件和目录操作中的应用。

1. 基础概念与模块

  • os: 提供平台无关的操作系统功能,如文件、进程、环境变量等操作。
  • *os.File: 表示一个打开的文件对象,包含了操作文件所需的所有信息。
  • 文件描述符 (File Descriptor): 操作系统内核用来跟踪打开文件和 I/O 资源的整数 ID。
  • defer file.Close(): 在 Go 语言中,打开文件后必须使用 defer 语句来确保文件被关闭,释放系统资源。

2. 文件操作:创建、打开与关闭

所有文件操作都围绕获取一个 *os.File 对象展开。

创建文件:os.Create

os.Create(name) 用于创建名为 name 的文件。如果文件已存在,则会清空其内容(截断为零长度)。默认权限是 0666

go
package main

import (
    "fmt"
    "os"
)

func main() {
    fileName := "test_create.txt"

    // os.Create 会返回 *os.File 和 error
    file, err := os.Create(fileName)
    if err != nil {
        fmt.Println("创建文件失败:", err)
        return
    }
    // 确保文件最终会被关闭
    defer file.Close() 

    fmt.Printf("文件 %s 创建成功!\n", fileName)
}

打开文件:os.OpenFile

os.OpenFile(name, flag, perm) 是最灵活的文件打开函数,它允许指定打开模式和文件权限。

  • name: 文件名。
  • flag: 文件打开模式,使用 os 包的常量组合。
  • perm: 文件权限(八进制数,如 0666)。
常用 flag 组合作用
os.O_RDONLY只读模式。
os.O_WRONLY只写模式。
os.O_RDWR读写模式。
os.O_APPEND追加模式,写入时从文件末尾开始。
os.O_CREATE如果文件不存在,则创建。
os.O_TRUNC如果文件已存在且为写模式,则清空文件内容。

示例:以追加模式打开文件,如果不存在则创建

go
file, err := os.OpenFile("test_append.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
    fmt.Println("打开文件失败:", err)
    return
}
defer file.Close()

// 写入内容时,将追加到文件末尾
file.WriteString("这是一条追加的日志。\n")

权限模式 (os.FileMode)

文件权限通常使用八进制数表示,例如:

  • 0644: 属主读写,组用户和其他用户只读。
  • 0777: 所有用户可读、写、执行。

3. 文件内容读写

获取到 *os.File 对象后,就可以进行内容的读写。

写入文件

  • file.Write([]byte): 写入字节切片。
  • file.WriteString(s string): 写入字符串(更方便)。
go
file, _ := os.Create("write_demo.txt")
defer file.Close()

// 写入字节切片
n1, _ := file.Write([]byte("Hello, Golang!\n"))
fmt.Printf("写入 %d 字节。\n", n1)

// 写入字符串
n2, _ := file.WriteString("这是第二行内容。\n")
fmt.Printf("写入 %d 字符。\n", n2)

读取文件

file.Read([]byte) 从文件中读取数据到指定的字节切片中,返回读取的字节数和错误。

go
file, _ := os.Open("write_demo.txt") // os.Open 是 os.OpenFile(name, os.O_RDONLY, 0) 的简化版
defer file.Close()

// 创建一个缓冲区
buffer := make([]byte, 1024)

// 循环读取,直到文件末尾
for {
    n, err := file.Read(buffer)
    if err != nil {
        if err == os.EOF { // 读取到文件末尾 (End Of File)
            break
        }
        fmt.Println("读取文件出错:", err)
        return
    }
    // 将读取到的字节转换为字符串打印
    fmt.Print(string(buffer[:n])) 
}

一次性读写

对于小型文件,os 包提供了便捷的函数 os.WriteFile / os.ReadFile,可以一次性完成文件的读写,无需手动打开和关闭。

go
// --- 写入 (os.WriteFile) ---
data := []byte("这是要写入的全部内容。")
// (文件名, 数据, 权限)
err := os.WriteFile("one_shot_write.txt", data, 0644) 
if err != nil {
    fmt.Println("一次性写入失败:", err)
}

// --- 读取 (os.ReadFile) ---
content, err := os.ReadFile("one_shot_write.txt")
if err != nil {
    fmt.Println("一次性读取失败:", err)
}
fmt.Println("\n一次性读取到的内容:", string(content))

4. 目录操作

创建目录

  • os.Mkdir(name, perm): 创建单个目录。如果父目录不存在,则会失败。
  • os.MkdirAll(path, perm): 创建目录及其所需的所有父目录
go
// 创建单层目录
err := os.Mkdir("temp_dir", 0755)
if err != nil && !os.IsExist(err) {
    fmt.Println("创建单层目录失败:", err)
}

// 创建多层目录 (推荐)
err = os.MkdirAll("path/to/my/data", 0755)
if err != nil {
    fmt.Println("创建多层目录失败:", err)
}

遍历目录

os.ReadDir(name) 用于读取指定目录下的所有文件和子目录项。

go
entries, err := os.ReadDir("./") // 读取当前目录
if err != nil {
    fmt.Println("读取目录失败:", err)
    return
}

fmt.Println("\n当前目录文件列表:")
for _, entry := range entries {
    if entry.IsDir() {
        fmt.Printf("[目录] %s\n", entry.Name())
    } else {
        fmt.Printf("[文件] %s\n", entry.Name())
    }
}

删除目录或文件

  • os.Remove(name): 删除文件或空目录。
  • os.RemoveAll(path): 递归删除 path 指定的目录及其所有内容,或删除单个文件。
go
// 删除单个文件
os.Remove("one_shot_write.txt") 

// 删除目录及其所有子内容
os.RemoveAll("path/to")

5. 文件信息与状态

获取文件信息:os.Stat

os.Stat(name) 返回一个 os.FileInfo 接口,其中包含了文件的大小、修改时间、权限等元数据。

go
fileInfo, err := os.Stat("test_create.txt")
if err != nil {
    fmt.Println("获取文件信息失败:", err)
    return
}

fmt.Println("\n--- 文件信息 ---")
fmt.Println("文件名:", fileInfo.Name())
fmt.Println("文件大小:", fileInfo.Size(), "字节")
fmt.Println("是否是目录:", fileInfo.IsDir())
fmt.Println("文件权限:", fileInfo.Mode())
fmt.Println("最后修改时间:", fileInfo.ModTime())

检查文件或目录是否存在

Go 语言中没有直接的 os.Exists 函数。最标准的做法是使用 os.Stat 结合 os.IsNotExist 检查错误。

go
_, err := os.Stat("file_to_check.txt")

if os.IsNotExist(err) {
    fmt.Println("文件不存在。")
} else if err != nil {
    fmt.Println("检查文件时发生错误:", err)
} else {
    fmt.Println("文件存在。")
}

6. 带缓冲的 I/O (bufio 包)

虽然 os 包提供了基本的读写功能,但进行大量小规模的 I/O 操作时,直接调用操作系统的 I/O 函数会导致性能下降。bufio 包通过在内存中添加缓冲区 (Buffer),减少了对系统底层 I/O 的调用次数,从而大幅提升了读写效率。

核心概念

类型作用对应接口
bufio.Writer带缓冲的写入器。先将数据写入内存缓冲区,缓冲区满或手动调用 Flush() 时,才批量写入底层 io.Writer (如 *os.File)。io.Writer
bufio.Reader带缓冲的读取器。从底层 io.Reader 中批量读取数据到内存,用户每次读取时从缓冲区获取。io.Reader
bufio.Scanner专为高效地按行或按分隔符读取输入而设计,常用于读取大文件。

示例一:使用 bufio.Writer 提升写入效率

当需要多次写入文件时,推荐使用 bufio.Writer

go
package main

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

func main() {
    file, err := os.OpenFile("buffered_write.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
    if err != nil {
        fmt.Println("打开文件失败:", err)
        return
    }
    defer file.Close()

    // 1. 创建 bufio.Writer,将其绑定到文件对象
    writer := bufio.NewWriter(file)
    
    // 确保在程序退出前,将缓冲区中所有数据写入文件
    defer writer.Flush() 

    // 2. 使用 writer 进行多次写入
    for i := 1; i <= 5; i++ {
        _, err := writer.WriteString(fmt.Sprintf("这是第 %d 行数据。\n", i))
        if err != nil {
            fmt.Println("写入缓冲区失败:", err)
            return
        }
    }
    
    fmt.Println("数据已写入缓冲区,等待 Flush 刷新到文件...")

    // 3. 手动调用 Flush() 将缓冲区内容写入磁盘
    // writer.Flush() // 如果没有 defer writer.Flush(),则需要在这里手动调用
}

示例二:使用 bufio.Scanner 按行读取大文件

bufio.Scanner 是读取文本文件(尤其是按行读取)最高效和最方便的方式。

go
package main

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

func main() {
    // 假设文件 "buffered_write.txt" 已经存在
    file, err := os.Open("buffered_write.txt")
    if err != nil {
        fmt.Println("打开文件失败:", err)
        return
    }
    defer file.Close()

    // 1. 创建 bufio.Scanner
    scanner := bufio.NewScanner(file)

    lineCount := 0
    // 2. 循环扫描,默认按行分割
    for scanner.Scan() {
        lineCount++
        line := scanner.Text() // 获取当前行的文本内容
        fmt.Printf("读取到第 %d 行: %s\n", lineCount, line)
    }

    // 3. 检查是否有扫描错误
    if err := scanner.Err(); err != nil {
        fmt.Println("扫描文件出错:", err)
    }
}

7. 关于 io/ioutil 包的说明

重要提示: 在 Go 1.16 及之后的版本中,io/ioutil 包已被废弃并迁移

之前常用的 ioutil.ReadFile()ioutil.WriteFile() 等函数,已经分别被迁移到 os.ReadFile()os.WriteFile()。因此,在现代 Go 代码中,应直接使用 os 包中的对应函数进行一次性读写,无需再导入 io/ioutil

8. 其他常用 os API

环境变量

函数作用
os.Getenv(key)获取指定环境变量的值。
os.Setenv(key, value)设置环境变量的值。
os.LookupEnv(key)查找并返回环境变量的值和是否存在 (value, present)。
go
path := os.Getenv("PATH")
fmt.Println("\nPATH 环境变量:", path)

// 设置并查找自定义变量
os.Setenv("GO_CUSTOM_VAR", "TEST_VALUE")
val, ok := os.LookupEnv("GO_CUSTOM_VAR")
if ok {
    fmt.Println("GO_CUSTOM_VAR 的值:", val)
}

工作目录

  • os.Getwd(): 获取当前工作目录 (Current Working Directory)。
  • os.Chdir(dir): 更改当前工作目录。
go
wd, _ := os.Getwd()
fmt.Println("当前工作目录:", wd)