• Go语言如何操作文件


    Go语言操作文件可使用的库

    Go语言官方库:os、io/ioutil、bufio涵盖了文件操作的所有场景,os提供了对文件IO直接调用的方法,bufio提供缓冲区操作文件的方法,io/ioutil也提供对文件IO直接调用的方法,不过Go语言在Go1.16版本已经弃用了io/ioutil库,这个io/ioutil包是一个定义不明确且难以理解的东西集合。该软件包提供的所有功能都已移至其他软件包,所以io/ioutil中操作文件的方法都在io库有相同含义的方法,大家以后在使用到ioutil中的方法是可以通过注释在其他包找到对应的方法。

    文件的基础操作

    这里我把 创建文件、打开文件、关闭文件、改变文件权限这些归为对文件的基本操作,对文件的基本操作直接使用os库中的方法即可,因为我们需要进行IO操作,来看下面的例子:

    import (
     "log"
     "os"
    )
    func main() {
     // 创建文件
     f, err := os.Create("asong.txt")
     if err != nil{
      log.Fatalf("create file failed err=%s\n", err)
     }
     // 获取文件信息
     fileInfo, err := f.Stat()
     if err != nil{
      log.Fatalf("get file info failed err=%s\n", err)
     }
    
     log.Printf("File Name is %s\n", fileInfo.Name())
     log.Printf("File Permissions is %s\n", fileInfo.Mode())
     log.Printf("File ModTime is %s\n", fileInfo.ModTime())
    
     // 改变文件权限
     err = f.Chmod(0777)
     if err != nil{
      log.Fatalf("chmod file failed err=%s\n", err)
     }
    
     // 改变拥有者
     err = f.Chown(os.Getuid(), os.Getgid())
     if err != nil{
      log.Fatalf("chown file failed err=%s\n", err)
     }
    
     // 再次获取文件信息 验证改变是否正确
     fileInfo, err = f.Stat()
     if err != nil{
      log.Fatalf("get file info second failed err=%s\n", err)
     }
     log.Printf("File change Permissions is %s\n", fileInfo.Mode())
    
     // 关闭文件
     err = f.Close()
     if err != nil{
      log.Fatalf("close file failed err=%s\n", err)
     }
     
     // 删除文件
     err = os.Remove("asong.txt")
     if err != nil{
      log.Fatalf("remove file failed err=%s\n", err)
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    写文件

    快写文件

    os/ioutil包都提供了WriteFile方法可以快速处理创建/打开文件/写数据/关闭文件,使用示例如下:

    func writeAll(filename string) error {
     err := os.WriteFile("asong.txt", []byte("Hi asong\n"), 0666)
     if err != nil {
      return err
     }
     return nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    按行写文件

    os、buffo写数据都没有提供按行写入的方法,所以我们可以在调用os.WriteString、bufio.WriteString方法是在数据中加入换行符即可,来看示例:

    import (
     "bufio"
     "log"
     "os"
    )
    // 直接操作IO
    func writeLine(filename string) error {
     data := []string{
      "asong",
      "test",
      "123",
     }
     f, err := os.OpenFile(filename, os.O_WRONLY, 0666)
     if err != nil{
      return err
     }
    
     for _, line := range data{
      _,err := f.WriteString(line + "\n")
      if err != nil{
       return err
      }
     }
     f.Close()
     return nil
    }
    // 使用缓存区写入
    func writeLine2(filename string) error {
     file, err := os.OpenFile(filename, os.O_WRONLY, 0666)
     if err != nil {
      return err
     }
    
     // 为这个文件创建buffered writer
     bufferedWriter := bufio.NewWriter(file)
     
     for i:=0; i < 2; i++{
      // 写字符串到buffer
      bytesWritten, err := bufferedWriter.WriteString(
       "asong真帅\n",
      )
      if err != nil {
       return err
      }
      log.Printf("Bytes written: %d\n", bytesWritten)
     }
     // 写内存buffer到硬盘
     err = bufferedWriter.Flush()
     if err != nil{
      return err
     }
    
     file.Close()
     return nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    偏移量写入

    某些场景我们想根据给定的偏移量写入数据,可以使用os中的writeAt方法,例子如下:

    import "os"
    
    func writeAt(filename string) error {
     data := []byte{
      0x41, // A
      0x73, // s
      0x20, // space
      0x20, // space
      0x67, // g
     }
     f, err := os.OpenFile(filename, os.O_WRONLY, 0666)
     if err != nil{
      return err
     }
     _, err = f.Write(data)
     if err != nil{
      return err
     }
    
     replaceSplace := []byte{
      0x6F, // o
      0x6E, // n
     }
     _, err = f.WriteAt(replaceSplace, 2)
     if err != nil{
      return err
     }
     f.Close()
     return nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    缓存区写入

    os库中的方法对文件都是直接的IO操作,频繁的IO操作会增加CPU的中断频率,所以我们可以使用内存缓存区来减少IO操作,在写字节到硬盘前使用内存缓存,当内存缓存区的容量到达一定数值时在写内存数据buffer到硬盘,bufio就是这样示一个库,来个例子我们看一下怎么使用:

    import (
     "bufio"
     "log"
     "os"
    )
    
    func writeBuffer(filename string) error {
     file, err := os.OpenFile(filename, os.O_WRONLY, 0666)
     if err != nil {
      return err
     }
    
     // 为这个文件创建buffered writer
     bufferedWriter := bufio.NewWriter(file)
    
     // 写字符串到buffer
     bytesWritten, err := bufferedWriter.WriteString(
      "asong真帅\n",
     )
     if err != nil {
      return err
     }
     log.Printf("Bytes written: %d\n", bytesWritten)
    
     // 检查缓存中的字节数
     unflushedBufferSize := bufferedWriter.Buffered()
     log.Printf("Bytes buffered: %d\n", unflushedBufferSize)
    
     // 还有多少字节可用(未使用的缓存大小)
     bytesAvailable := bufferedWriter.Available()
     if err != nil {
      return err
     }
     log.Printf("Available buffer: %d\n", bytesAvailable)
     // 写内存buffer到硬盘
     err = bufferedWriter.Flush()
     if err != nil{
      return err
     }
    
     file.Close()
     return nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    读文件

    读取全文件

    有两种方式我们可以读取全文件:

    os、io/ioutil中提供了readFile方法可以快速读取全文
    io/ioutil中提供了ReadAll方法在打开文件句柄后可以读取全文;

    import (
     "io/ioutil"
     "log"
     "os"
    )
    
    func readAll(filename string) error {
     data, err := os.ReadFile(filename)
     if err != nil {
      return err
     }
     log.Printf("read %s content is %s", filename, data)
     return nil
    }
    
    func ReadAll2(filename string) error {
     file, err := os.Open("asong.txt")
     if err != nil {
      return err
     }
    
     content, err := ioutil.ReadAll(file)
     log.Printf("read %s content is %s\n", filename, content)
    
     file.Close()
     return nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    逐行读取

    os库中提供了Read方法是按照字节长度读取,如果我们想要按行读取文件需要配合bufio一起使用,bufio中提供了三种方法ReadLine、ReadBytes(“\n”)、ReadString(“\n”)可以按行读取数据,下面我使用ReadBytes(“\n”)来写个例子:

    func readLine(filename string) error {
     file, err := os.OpenFile(filename, os.O_RDONLY, 0666)
     if err != nil {
      return err
     }
     bufferedReader := bufio.NewReader(file)
     for {
      // ReadLine is a low-level line-reading primitive. Most callers should use
      // ReadBytes('\n') or ReadString('\n') instead or use a Scanner.
      lineBytes, err := bufferedReader.ReadBytes('\n')
      bufferedReader.ReadLine()
      line := strings.TrimSpace(string(lineBytes))
      if err != nil && err != io.EOF {
       return err
      }
      if err == io.EOF {
       break
      }
      log.Printf("readline %s every line data is %s\n", filename, line)
     }
     file.Close()
     return nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    按块读取文件

    有些场景我们想按照字节长度读取文件,这时我们可以如下方法:

    os库的Read方法
    os库配合bufio.NewReader调用Read方法
    os库配合io库的ReadFull、ReadAtLeast方法

    // use bufio.NewReader
    func readByte(filename string) error {
     file, err := os.OpenFile(filename, os.O_RDONLY, 0666)
     if err != nil {
      return err
     }
     // 创建 Reader
     r := bufio.NewReader(file)
    
     // 每次读取 2 个字节
     buf := make([]byte, 2)
     for {
      n, err := r.Read(buf)
      if err != nil && err != io.EOF {
       return err
      }
    
      if n == 0 {
       break
      }
      log.Printf("writeByte %s every read 2 byte is %s\n", filename, string(buf[:n]))
     }
     file.Close()
     return nil
    }
    
    // use os
    func readByte2(filename string) error{
     file, err := os.OpenFile(filename, os.O_RDONLY, 0666)
     if err != nil {
      return err
     }
    
     // 每次读取 2 个字节
     buf := make([]byte, 2)
     for {
      n, err := file.Read(buf)
      if err != nil && err != io.EOF {
       return err
      }
    
      if n == 0 {
       break
      }
      log.Printf("writeByte %s every read 2 byte is %s\n", filename, string(buf[:n]))
     }
     file.Close()
     return nil
    }
    
    
    // use os and io.ReadAtLeast
    func readByte3(filename string) error{
     file, err := os.OpenFile(filename, os.O_RDONLY, 0666)
     if err != nil {
      return err
     }
    
     // 每次读取 2 个字节
     buf := make([]byte, 2)
     for {
      n, err := io.ReadAtLeast(file, buf, 0)
      if err != nil && err != io.EOF {
       return err
      }
    
      if n == 0 {
       break
      }
      log.Printf("writeByte %s every read 2 byte is %s\n", filename, string(buf[:n]))
     }
     file.Close()
     return nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    分隔符读取

    bufio包中提供了Scanner扫描器模块,它的主要作用是把数据流分割成一个个标记并除去它们之间的空格,他支持我们定制Split函数做为分隔函数,分隔符可以不是一个简单的字节或者字符,我们可以自定义分隔函数,在分隔函数实现分隔规则以及指针移动多少,返回什么数据,如果没有定制Split函数,那么就会使用默认ScanLines作为分隔函数,也就是使用换行作为分隔符,bufio中还提供了默认方法ScanRunes、ScanWrods,下面我们用SacnWrods方法写个例子,获取用空格分隔的文本:

    func readScanner(filename string) error {
     file, err := os.OpenFile(filename, os.O_RDONLY, 0666)
     if err != nil {
      return err
     }
    
     scanner := bufio.NewScanner(file)
     // 可以定制Split函数做分隔函数
     // ScanWords 是scanner自带的分隔函数用来找空格分隔的文本字
     scanner.Split(bufio.ScanWords)
     for {
      success := scanner.Scan()
      if success == false {
       // 出现错误或者EOF是返回Error
       err = scanner.Err()
       if err == nil {
        log.Println("Scan completed and reached EOF")
        break
       } else {
        return err
       }
      }
      // 得到数据,Bytes() 或者 Text()
      log.Printf("readScanner get data is %s", scanner.Text())
     }
     file.Close()
     return nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    打包/解包

    Go语言的archive包中提供了tar、zip两种打包/解包方法,这里以zip的打包/解包为例子:

    zip解包示例:

    import (
     "archive/zip"
     "fmt"
     "io"
     "log"
     "os"
    )
    
    func main()  {
     // Open a zip archive for reading.
     r, err := zip.OpenReader("asong.zip")
     if err != nil {
      log.Fatal(err)
     }
     defer r.Close()
     // Iterate through the files in the archive,
     // printing some of their contents.
     for _, f := range r.File {
      fmt.Printf("Contents of %s:\n", f.Name)
      rc, err := f.Open()
      if err != nil {
       log.Fatal(err)
      }
      _, err = io.CopyN(os.Stdout, rc, 68)
      if err != nil {
       log.Fatal(err)
      }
      rc.Close()
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    zip打包示例:

    func writerZip()  {
     // Create archive
     zipPath := "out.zip"
     zipFile, err := os.Create(zipPath)
     if err != nil {
      log.Fatal(err)
     }
    
     // Create a new zip archive.
     w := zip.NewWriter(zipFile)
     // Add some files to the archive.
     var files = []struct {
      Name, Body string
     }{
      {"asong.txt", "This archive contains some text files."},
      {"todo.txt", "Get animal handling licence.\nWrite more examples."},
     }
     for _, file := range files {
      f, err := w.Create(file.Name)
      if err != nil {
       log.Fatal(err)
      }
      _, err = f.Write([]byte(file.Body))
      if err != nil {
       log.Fatal(err)
      }
     }
     // Make sure to check the error on Close.
     err = w.Close()
     if err != nil {
      log.Fatal(err)
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    总结

    本文归根结底是介绍os、io、bufio这些包如何操作文件,因为Go语言操作提供了太多了方法,借着本文全都介绍出来,在使用的时候可以很方便的当作文档查询,如果你问用什么方法操作文件是最优的方法,这个我也没法回答你,需要根据具体场景分析的,如果这些方法你都知道了,在写一个benchmark对比一下就可以了,实践才是检验真理的唯一标准。

    来源公众号: Golang梦工厂: 超全总结:Go语言如何操作文件

  • 相关阅读:
    国内多位架构大牛强烈推荐的大型分布式手册
    【新品发布】原来做多用途(室内、室外)无人机开发还能用它——P450进阶版
    【Node.JS】buffer类缓冲区
    【C语言】初阶指针的简单使用 _指针运算 &指针和数组的关系[初阶篇 _学习专用]
    从零开始写 Docker(十八)---容器网络实现(下):为容器插上”网线“
    Apple官网的动效
    4K \1080P 高质量视频素材网站。
    如何实现点云体素化及由密集特征得到二维伪装
    Linux单列模式实现线程池
    查询表中指定列数据
  • 原文地址:https://blog.csdn.net/weixin_43064185/article/details/126095860