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。
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 | 如果文件已存在且为写模式,则清空文件内容。 |
示例:以追加模式打开文件,如果不存在则创建
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): 写入字符串(更方便)。
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) 从文件中读取数据到指定的字节切片中,返回读取的字节数和错误。
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,可以一次性完成文件的读写,无需手动打开和关闭。
// --- 写入 (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): 创建目录及其所需的所有父目录。
// 创建单层目录
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) 用于读取指定目录下的所有文件和子目录项。
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 指定的目录及其所有内容,或删除单个文件。
// 删除单个文件
os.Remove("one_shot_write.txt")
// 删除目录及其所有子内容
os.RemoveAll("path/to")5. 文件信息与状态
获取文件信息:os.Stat
os.Stat(name) 返回一个 os.FileInfo 接口,其中包含了文件的大小、修改时间、权限等元数据。
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 检查错误。
_, 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。
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 是读取文本文件(尤其是按行读取)最高效和最方便的方式。
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)。 |
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): 更改当前工作目录。
wd, _ := os.Getwd()
fmt.Println("当前工作目录:", wd)