go 中的文件操作

文件操作是编程中非常常见的任务,Go 语言提供了丰富的标准库来处理文件操作。本篇文章将详细介绍 Go 语言中的文件操作,涵盖文件的创建、读取、写入、追加、删除、重命名、权限管理等内容,并通过代码示例帮助你轻松理解。


文件的创建与打开

创建文件

在 Go 中,可以使用 os.Create 函数创建一个新文件。如果文件已存在,os.Create 会清空文件内容。

package fileop

import (
    "fmt"
    "os"
)

func CreateFile(path string) error {
    f, err := os.Create(path)
    if err != nil {
        fmt.Println("文件创建失败")
        return err
    }
    defer f.Close()
    fmt.Println("文件创建成功")
    return nil
}
  • os.Create 会创建一个新文件,并返回一个 *os.File 对象和 error
  • 如果文件已存在,os.Create 会将其截断为空文件。
  • 使用 defer file.Close() 确保文件在操作完成后关闭。

打开文件

使用 os.Open 可以打开一个已存在的文件,但只能以只读模式打开。

package fileop

import (
    "fmt"
    "os"
)

func Open(path string) error {
    f, err := os.Open(path)
    if err != nil {
        fmt.Println("文件打开失败")
        return err
    }
    defer f.Close()

    fmt.Println("文件打开成功")
    return nil
}
  • os.Open 以只读模式打开文件。
  • 如果需要以其他模式(如写入或追加)打开文件,可以使用 os.OpenFile

使用 os.OpenFile 打开文件

os.OpenFile 提供了更灵活的文件打开方式,可以指定文件的打开模式和权限。

package main

import (
    "fmt"
    "os"
)

func OpenFileWithFlag(path string, flag int) error {
    f, err := os.OpenFile(path, flag, 0666)
    if err != nil {
        fmt.Println("文件打开失败")
        return err
    }
    defer f.Close()

    fmt.Println("文件打开成功")
    fmt.Println("文件打开方式:", f.Fd())
    return nil
}

func TestOpenFileWithFlag(t *testing.T) {
    flag := os.O_RDWR | os.O_CREATE
    err := fileop.OpenFileWithFlag("test.txt", flag)
    if err != nil {
        t.Errorf("OpenFileWithFlag() error = %v", err)
    }
    t.Log("OpenFileWithFlag() success")
}
  • os.OpenFile 的三个参数分别是文件名、打开模式和文件权限。
  • 打开模式:
    • os.O_RDONLY:只读
    • os.O_WRONLY:只写
    • os.O_RDWR:读写
    • os.O_CREATE:如果文件不存在则创建
    • os.O_APPEND:追加写入
    • os.O_TRUNC:清空文件
    • 等等…
  • 文件权限使用 Unix 风格的权限位,例如 0644 表示文件所有者可读写,其他人只读。

如果你查看一下源码就会发现,实际上os.Open背后就是调用的 os.Openfile使用只读权限来打开文件。

文件打开模式

在计算机系统中,每个标志位实际上对应了一个二进制位。例如os.O_RDWR、os.O_CREATE等每个常量都有一个特定的二进制表示,它们各自占据位的不同位置。通过或操作,可以将多个标志位合并到一个整数中,每个标志位在结果中仍然保持独立。当使用如os.O_RDWR | os.O_CREATE这样的组合时,实际上是在设置一个二进制整数,其中的某些位被设置为1,表示相应的模式被启用。在文件系统调用中,操作系统会解析这个整数,根据各个位的状态来决定以何种模式打开文件。

例如,os.O_RDWR可能是二进制00000010,而os.O_CREATE可能是二进制00000100。当它们进行或操作时,结果是00000110,这个结果同时包含了读写和创建文件的功能。

因此,通过位或操作,Go 语言中的文件打开模式可以组合多个标志,从而实现灵活的文件操作。这种机制在许多编程语言和操作系统中都普遍存在,它提供了一种高效且简洁的方式来设置和组合多种选项。


文件的读取

读取整个小文件

使用 ioutil.ReadFile 可以一次性写入整个文件内容。从 Go 1.16 开始,推荐使用 os.ReadFile 替代 ioutil.ReadFile

package fileop

import (
    "fmt"
    "os"
)

func ReadFile(path string) ([]byte, error) {
    b1, err := os.ReadFile(path)
    if err != nil {
        fmt.Println("文件读取失败")
        return nil, err
    }

    fmt.Printf("文件读取成功, b1: %v\n", string(b1[:]))
    return b1, nil
}
  • os.ReadFile 返回文件的字节切片,适合读取小文件。
  • 对于大文件,这种方式可能会导致内存占用过高。

逐行读取文件

使用bufio.Scanner可以逐行读取文件内容,适合处理大文件。

func ReadFileWithBufIO(path string) error {
    f, err := os.Open(path)
    if err != nil {
        fmt.Println("文件打开失败")
        return err
    }
    defer f.Close()

    scanner := bufio.NewScanner(f)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }

    if err := scanner.Err(); err != nil {
        fmt.Printf("读取文件失败,err %v\n", err.Error())
        return err
    }

    return nil
}
  • bufio.Scanner 会逐行读取文件内容。
  • scanner.Text() 返回当前行的字符串内容。

按字节读取文件

使用文件实例的Read方法可以按字节读取文件内容。

func ReadFileWithBytes(path string) error {
    b1, err := os.Open(path)
    if err != nil {
        fmt.Println("打开文件失败")
        return err
    }
    defer b1.Close()

    for {
        buf := make([]byte, 5)
        n, err := b1.Read(buf)
        if err != nil {
            if err.Error() == "EOF" {
                break
            }
            fmt.Println(err.Error())
            return err
        }

        fmt.Printf("读取到 %d 个字节成功,内容:%v\n", n, buf)
    }

    b1.Seek(0, 0)
    for {
        buf := make([]byte, 2)
        n, err := b1.Read(buf)
        if err != nil {
            if err.Error() == "EOF" {
                break
            }
            fmt.Println(err.Error())
            return err
        }

        fmt.Printf("读取到 %d 个字节成功,内容:%v\n", n, buf)
    }
    return nil
}
  • file.Read 会读取文件内容到字节切片中,返回读取的字节数。
  • 适合需要精确控制读取字节数的场景,需要选择合适的 buf 大小。
  • 读取过程中会造成指针的偏移,使用 Seek 方法可以重置指针的位置。
  • 往 buf 缓冲中写入数据时,最后一次读取数据时需要注意手动处理,可能会读不满。

文件的写入

写入整个文件

使用 ioutil.WriteFile 可以一次性写入整个文件内容。从 Go 1.16 开始,推荐使用 os.WriteFile 替代 ioutil.WriteFile

package fileop

import (
    "fmt"
    "os"
)

func WriteFile(path string, data []byte) error {
    err := os.WriteFile(path, data, os.ModePerm)
    if err != nil {
        fmt.Printf("err: %v\n", err)
        return err
    }
    fmt.Printf("文件写入成功\n")
    return nil
}

源码:

func WriteFile(name string, data []byte, perm FileMode) error {
    f, err := OpenFile(name, O_WRONLY|O_CREATE|O_TRUNC, perm)
    if err != nil {
        return err
    }
    _, err = f.Write(data)
    if err1 := f.Close(); err1 != nil && err == nil {
        err = err1
    }
    return err
}

查看源码可以发现,WriteFile方法实际是先使用OpenFile函数来打开文件,打开模式为O_WRONLY|O_CREATE|O_TRUNC,然后调用文件实例对象的Write方法将内容写入文件。

追加写入文件

看过os.WriteFile函数的源码后可知,文件写入实际就是调用文件对象实例的 Write 方法实现的,而如何写入,取决于使用 os.OpenFile 打开文件时的 flag,因此我们可以自行指定 flag os.O_APPEND来实现追加写入。

package fileop

import (
    "fmt"
    "os"
)

func WriteFileWithAppend(path string) error {
    f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, os.ModePerm)
    if err != nil {
        fmt.Println("打开文件出错:", err)
        return err
    }
    defer f.Close()

    data := []byte("\n追加的内容")
    _, err = f.Write(data)
    if err != nil {
        fmt.Println("写入文件出错:", err)
        return err
    }

    fmt.Printf("追加写入成功\n")
    return nil
}
  • os.O_APPEND 标志表示追加写入。
  • file.Write 用于写入字节切片。

文件的删除与重命名

删除文件

使用 os.Remove 可以删除文件。

package fileop

import "os"

func RemoveFile(path string) error {
    err := os.Remove(path)
    if err != nil {
        return err
    }
    return nil
}
  • os.Remove 用于删除文件或空目录。

重命名文件

使用 os.Rename 可以重命名文件或移动文件。

package fileop

import "os"

func RenameFile(oldPath, newPath string) error {
    err := os.Rename(oldPath, newPath)
    if err != nil {
        return err
    }
    return nil
}

测试:

package fileop_test

import (
    "testing"

    "github.com/wuvikr/go-study/core/fileop"
)

func TestRenamefile(t *testing.T) {
    err := fileop.RenameFile("test.txt", "tmp/test1.txt")
    if err != nil {
        t.Error(err)
    }
}
  • os.Rename 可以重命名文件,也可以将文件移动到其他目录。

文件权限管理

修改文件权限

使用 os.Chmod 可以修改文件的权限。

package fileop

import "os"

func ChmodFile(path string, mode os.FileMode) error {
    err := os.Chmod(path, mode)
    if err != nil {
        return err
    }
    return nil
}
  • os.Chmod 用于修改文件的权限,0755 表示文件所有者可读写执行,其他人可读执行。

获取文件信息

使用 os.Stat 可以获取文件的详细信息。

package fileop

import (
    "fmt"
    "os"
)

func StatFile(path string) error {
    info, err := os.Stat(path)
    if err != nil {
        return err
    }
    fmt.Printf("文件信息: %v\n", info)
    fmt.Printf("文件名称: %v\n", info.Name())
    fmt.Printf("文件大小: %v\n", info.Size())
    fmt.Printf("文件权限: %v\n", info.Mode())
    fmt.Printf("文件修改时间: %v\n", info.ModTime())
    fmt.Printf("文件是否为目录: %v\n", info.IsDir())
    return nil
}
  • os.Stat 返回一个 os.FileInfo 对象,包含文件的名称、大小、权限、修改时间等信息。

目录操作

创建目录

使用 os.Mkdir 可以创建单个目录。

package fileop

import (
    "fmt"
    "os"
)

func CreateDir(path string, mode os.FileMode) error {
    if err := os.Mkdir(path, mode); err != nil {
        fmt.Println("创建目录失败, err:", err)
        return err
    }
    fmt.Println("创建目录成功")
    return nil
}

创建多级目录

使用 os.MkdirAll 可以创建多级目录。

func CreateDirAll(path string, mode os.FileMode) error {
    if err := os.MkdirAll(path, mode); err != nil {
        fmt.Println("创建多级目录失败, err:", err)
        return err
    }
    fmt.Println("创建多级目录成功")
    return nil
}
  • os.MkdirAll 会递归创建所有不存在的目录。

总结

本文详细介绍了 Go 语言中的文件操作,包括文件的创建、读取、写入、追加、删除、重命名、权限管理以及目录操作。

掌握这些基础操作将为你后续的开发工作打下坚实的基础。希望本文对你有所帮助,Happy coding!

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 1

我还以为能雕出什么花来呢,这样容易让老师傅怀疑人生的。

4天前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!