18 文件操作
2025/10/3大约 3 分钟
18 文件操作
Go 提供了多种文件操作方式,从底层的 os 包到高层的 ioutil 包,操作就是经典的打开、创建、写入、删除等操作。
打开和关闭文件
使用 os.Open()
打开文件,返回文件对象和错误:
file, err := os.Open("./main.go")
if err != nil {
fmt.Println("打开文件失败:", err)
return
}
defer file.Close() // 确保文件关闭
由于文件打开后必须关闭,但是后面可能有多个操作出现异常,因此建议在打开文件的异常处理后使用 defer 来关闭文件,避免资源泄漏。
读取文件
os 基础读取
可以使用 os 中的 Read()
方法每次读取指定字节数:
file, err := os.Open("./test.txt")
if err != nil {
fmt.Println("打开失败:", err)
return
}
defer file.Close()
var tmp = make([]byte, 128)
n, err := file.Read(tmp)
if err == io.EOF {
fmt.Println("文件读完了")
return
}
if err != nil {
fmt.Println("读取失败:", err)
return
}
fmt.Printf("读取了 %d 字节: %s\n", n, string(tmp[:n]))
但是上面的方法需要直接分配固定大小的切片,可能导致一部分的浪费,因此,也可以使用循环读取的方案来读长文件:
file, err := os.Open("./test.txt")
if err != nil {
return
}
defer file.Close()
var content []byte
var tmp = make([]byte, 128)
for {
n, err := file.Read(tmp)
if err == io.EOF {
break
}
if err != nil {
fmt.Println("读取失败:", err)
return
}
content = append(content, tmp[:n]...)
}
fmt.Println(string(content))
bufio 按行读取
bufio
提供了更高级的读取功能,支持按行读取:
file, err := os.Open("./test.txt")
if err != nil {
return
}
defer file.Close()
reader := bufio.NewReader(file)
for {
line, err := reader.ReadString('\n')
if err == io.EOF {
if len(line) != 0 {
fmt.Print(line)
}
break
}
if err != nil {
fmt.Println("读取失败:", err)
return
}
fmt.Print(line)
}
ioutil 一次读取
ioutil.ReadFile()
最简单,直接读取整个文件:
content, err := ioutil.ReadFile("./test.txt")
if err != nil {
fmt.Println("读取失败:", err)
return
}
fmt.Println(string(content))
写入文件
打开文件
使用 os.OpenFile()
指定打开模式:
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
...
}
第二个参数 flag 主要有如下几种,含义见名知意:
O_RDONLY,O_WRONLY,O_RDWR,O_CREATE,O_TRUNC,O_APPEND
可以进行组合,如 os.O_CREATE | os.O_WRONLY
表示创建并只写。
第三个参数 和 linux 文件是一样的,如 0666 表示所有人可读可写。
os 基础写入
file, err := os.OpenFile("./test.txt", os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
return
}
defer file.Close()
file.Write([]byte("Hello World")) // 写入字节切片
file.WriteString("你好 Golang\n") // 写入字符串
bufio 缓冲写入
对于频繁写入,使用缓冲可以提高性能:
file, err := os.OpenFile("./test.txt", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
if err != nil {
return
}
defer file.Close()
writer := bufio.NewWriter(file)
for i := 0; i < 10; i++ {
writer.WriteString(fmt.Sprintf("第 %d 行\n", i))
}
writer.Flush() // 必须调用 Flush 才会真正写入
这个方法会先写入缓冲区,必须调用 Flush()
才会写入文件。
ioutil 一次写入
这个是最简单的写入方式,会直接覆盖原有内容:
content := "Hello Golang"
err := ioutil.WriteFile("./test.txt", []byte(content), 0666)
if err != nil {
fmt.Println("写入失败:", err)
}
文件和目录操作
重命名/移动文件
err := os.Rename("./old.txt", "./new.txt")
if err != nil {
fmt.Println("重命名失败:", err)
}
复制文件
对于小文件,可以简单使用 ioutil 完成:
func CopyFile(dst, src string) error {
data, err := ioutil.ReadFile(src)
if err != nil {
return err
}
return ioutil.WriteFile(dst, data, 0644)
}
func main() {
err := CopyFile("./backup.txt", "./source.txt")
if err != nil {
fmt.Println("复制失败:", err)
}
}
对于大文件,还是用 os 的方法进行流式复制比较好:
func CopyFile(dst, src string) error {
source, err := os.Open(src)
if err != nil {
return err
}
defer source.Close()
destination, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
return err
}
defer destination.Close()
buf := make([]byte, 4096)
for {
n, err := source.Read(buf)
if err != nil && err != io.EOF {
return err
}
if n == 0 {
break
}
if _, err := destination.Write(buf[:n]); err != nil {
return err
}
}
return nil
}
创建目录
// 创建单个目录
err := os.Mkdir("./test", 0755)
if err != nil {
fmt.Println("创建失败:", err)
}
// 创建多级目录
err := os.MkdirAll("./a/b/c", 0755)
if err != nil {
fmt.Println("创建失败:", err)
}
删除文件/目录
// 删除单个文件或空目录
err := os.Remove("./test.txt")
if err != nil {
fmt.Println("删除失败:", err)
}
// 递归删除目录及其内容
err := os.RemoveAll("./test_dir")
if err != nil {
fmt.Println("删除失败:", err)
}
后者会递归删除所有内容,使用时需谨慎。