十四、Go语法进阶(文件操作)

41 阅读7分钟

Golang中文学习文档地址

Go提供的文件处理标准库

  • os
    负责OS文件系统交互的具体实现。
  • io
    读写 IO 的抽象层。
  • fs
    文件系统的抽象层。

1、打开文件

Go中有两个函数用于打开文件:os.OpenFileos.Open

1.1 os.OpenFile

func OpenFile(name string, flag int, perm FileMode) (*File, error)

参数说明:

  • name:文件名称

    文件的名称,会项目go.mod文件所在的路径下查找该文件。

  • flag:打开标志(打开模式)

    控制文件的打开方式和行为。

    const (
        // Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified.
        O_RDONLY int = syscall.O_RDONLY // open the file read-only.
        O_WRONLY int = syscall.O_WRONLY // open the file write-only.
        O_RDWR   int = syscall.O_RDWR   // open the file read-write.
        // The remaining values may be or'ed in to control behavior.
        O_APPEND int = syscall.O_APPEND // append data to the file when writing.
        O_CREATE int = syscall.O_CREAT  // create a new file if none exists.
        O_EXCL   int = syscall.O_EXCL   // used with O_CREATE, file must not exist.
        O_SYNC   int = syscall.O_SYNC   // open for synchronous I/O.
        O_TRUNC  int = syscall.O_TRUNC  // truncate regular writable file when opened.
    )
    
    标志说明
    os.O_RDONLY只读模式
    os.O_WRONLY只写模式
    os.O_RDWR读写模式
    os.O_CREATE如果文件不存在则创建
    os.O_APPEND追加模式(写入时添加到文件末尾)
    os.O_TRUNC打开时清空文件
    os.O_EXCL与 O_CREATE 一起使用,文件必须不存在

    可以组合使用,使用|组合两个标志:os.O_RDONLY | os.O_WRONLY

  • perm:文件权限

    • 文件权限位解析

      // 权限位分解:
      0  6 6  6
      │ │ │ └── 其他用户权限: 6 (读+写)
      │ │ └── 同组用户权限: 6 (读+写)  
      │ └── 文件所有者权限: 6 (读+写)
      └── 特殊权限位: 0 (无特殊权限)
      
      // 权限数值含义:
      // 4 = 读权限 (r)
      // 2 = 写权限 (w) 
      // 1 = 执行权限 (x)
      // 6 = 4+2 = 读 + 写
      // 7 = 4+2+1 = 读 + 写 + 执行
      
    • 文件权限位,使用 Unix 风格的权限表示

    • 仅在创建新文件时生效

      权限说明
      0644用户读写,组和其他只读
      0755用户读写执行,组和其他读执行
      0600仅用户读写
      0666所有用户可读写

1.2 os.Open

func Open(name string) (*File, error)

底层是对os.OpenFile的再一次封装。

func Open(name string) (*File, error) {
    //文件模式只读,perm=0表示没有设置任何权限位
    return OpenFile(name, O_RDONLY, 0)
}
  • perm=0

    表示没有设置任何权限位,在文件存在的时候,该perm参数会被忽略;但如果是创建文件,perm=0,创建的文件会表现为不可读、不可写、不可执行

1.3 其他相关函数

  • os.IsNotExist(err)

    通过返回的错误判断是否文件不存在

    func main() {
            file, err := os.Open("read1.md")
            defer file.Close()
            if os.IsNotExist(err) {
                    fmt.Println("File does not exist")
            }
            if err != nil {
                    fmt.Println(err)
            }
            fmt.Println(file.Name())
    }
    

2、读取文件

文件的读取,*os.File类型提供了两个的函数:

// 将文件读进传入的字节切片
func (f *File) Read(b []byte) (n int, err error)

// 相较于第一种可以从指定偏移量读取
func (f *File) ReadAt(b []byte, off int64) (n int, err error)

2.1 Read(b []byte)读取文件

package main

import (
    "errors"
    "fmt"
    "io"
    "os"
)

func main() {
    file, err := os.Open("read.md")
    defer file.Close()
    if os.IsNotExist(err) {
       fmt.Println("File does not exist")
       return
    }
    if err != nil {
       fmt.Println(err)
       return
    }
    readFile, err := ReadFile(file)
    fmt.Println(string(readFile))
}

// 读取文件
func ReadFile(file *os.File) ([]byte, error) {
    readBuffer := make([]byte, 0, 0)
    fmt.Println("初始长度:", len(readBuffer), "初始容量:", cap(readBuffer))
    count := 0
    for {
       if len(readBuffer) == cap(readBuffer) {
          count = count + 1
          fmt.Println("扩容次数:", count)
          // 扩容,当长度和容量一样,切片添加一个元素,Go底层会创建新的切片底层数组,并做扩容
          tempBuffer := append(readBuffer, 0)
          //添加一个元素后,需要将添加的元素去除,变成和原始的切片一样内容
          readBuffer = tempBuffer[:len(readBuffer)]
       }
       // 继续读取文件
       offset, err := file.Read(readBuffer[len(readBuffer):cap(readBuffer)])
       fmt.Println("读取的内容:", string(readBuffer))
       // 将已写入的数据归入切片
       readBuffer = readBuffer[:len(readBuffer)+offset]
       fmt.Println("归入后的内容:", string(readBuffer))
       // 发生错误时
       if err != nil {
          if errors.Is(err, io.EOF) {
             err = nil
          }
          return readBuffer, err
       }
    }
}

2.2 Go提供两个方便读取文件的函数

2.2.1 os.ReadFile

  • func ReadFile(name string) ([]byte, error)
func main() {
	file, err := os.ReadFile("read.md")
	if err != nil {
		panic(err)
	}
	fmt.Println(string(file))
}

2.2.2 io.ReadAll

  • func ReadAll(r Reader) ([]byte, error)
     func main() {
        openFile, err := os.Open("read.md")
        if err != nil {
           panic(err)
        }
    
        all, err := io.ReadAll(openFile)
        if err != nil {
           panic(err)
        }
        fmt.Println(string(all))
    }
    

3、 写入文件内容

3.1 os.File结构体提供的写入函数

// 写入字节切片
func (f *File) Write(b []byte) (n int, err error)

// 写入字符串
func (f *File) WriteString(s string) (n int, err error)

// 从指定位置开始写,当以os.O_APPEND模式打开时,会返回错误
func (f *File) WriteAt(b []byte, off int64) (n int, err error)
  • 实例:以os.O_APPEND写入文件,写入的数据会添加到文件尾部。
    func main() {
        file, err := os.OpenFile("read.md", os.O_WRONLY|os.O_TRUNC|os.O_CREATE|os.O_APPEND, 0666)
        if err != nil {
            panic(err)
        }
        defer file.Close()
        for i := range 10 {
            offset, err := file.WriteString("序号:" + strconv.FormatInt(int64(i), 10) + "\n")
            if err != nil {
                panic(err)
            }
            fmt.Println(offset)
        }
        readFile, err := os.ReadFile("read.md")
        if err != nil {
            panic(err)
        }
        fmt.Println(string(readFile))
    }
    

3.2 Go提供两个方便写入文件的函数

3.2.1 os.WriteFile

  • func WriteFile(name string, data []byte, perm FileMode) error
func main() {
	err := os.WriteFile("hello.txt", []byte("Hello, World!\n"), 0777)
	if err != nil {
		panic(err)
	}
	fmt.Println("写入完成")
}

3.2.2 io.WriteString

  • func WriteString(w Writer, s string) (n int, err error)
    func main() {
        //os.O_WRONLY:可读可写
        //os.O_CREATE:文件不存在时,创建
        //os.O_APPEND:从文件尾部写入数据
        //os.O_TRUNC:文件存在时,将数据截掉(即清空文件内容)
        file, err := os.OpenFile("openFile.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND|os.O_TRUNC, 0666)
        if err != nil {
            panic(err)
        }
        for i := range 10 {
            n, err := io.WriteString(file, "hello "+strconv.Itoa(i)+"\n")
            if err != nil {
                panic(err)
            }
            fmt.Println(n)
        }
    
    }
    

4、复制

文件的复制,需要同时打开两个文件。

Go中有三种方式实现文件的赋值:

4.1 将原文件读取,然后写入新的文件

func main() {
	file, err := os.ReadFile("hello.txt")
	if err != nil {
		panic(err)
	}
	err = os.WriteFile("hello1.txt", file, 0666)
	if err != nil {
		panic(err)
	}
}

4.2 使用os.File.ReadFrom

  • func (f *File) ReadFrom(r io.Reader) (n int64, err error)
  • 该复制方式,是将所有内容读取到内存中,然后在写到新的文件中去,在大数据量的文件中,该方式不适合,会造成内存容量不足。
func main() {
    //只读打开原始文件
    file, err := os.OpenFile("hello.txt", os.O_RDONLY, 0666)
    if err != nil {
       panic(err)
    }
    defer file.Close()
    
    //打开新的只读文件
    newFile, err := os.OpenFile("hello2.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666)
    if err != nil {
       panic(err)
    }
    defer newFile.Close()
    n, err := newFile.ReadFrom(file)
    if err != nil {
       panic(err)
    }
    fmt.Printf("%d bytes read\n", n)
}

4.3 使用io.Copy/io.CopyBuffer

  • func Copy(dst Writer, src Reader) (written int64, err error)。
  • io.CopyBuffer:可以指定缓冲区的大小。
  • 一边读一边写,先将内容读到缓冲区中,再写入到目标文件中,缓冲区默认大小为 32KB。
func main() {
    //只读打开原始文件
    file, err := os.OpenFile("hello.txt", os.O_RDONLY, 0666)
    if err != nil {
       panic(err)
    }
    defer file.Close()

    //打开新的只读文件
    newFile, err := os.OpenFile("hello3.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666)
    if err != nil {
       panic(err)
    }
    defer newFile.Close()
    written, err := io.Copy(newFile, file)
    if err != nil {
       panic(err)
    }
    fmt.Printf("%d bytes written\n", written)
}

5、重命名

  • os包下的rename函数
  • func Rename(oldpath, newpath string) error
func main() {
    err := os.Rename("hello3.txt", "copy.txt")
    if err != nil {
       panic(err)
    }
    fmt.Println("文件修改完成")
}

6、删除

  • 删除单个文件或者空目录,当目录不为空时会返回错误

    func Remove(name string) error

  • 删除指定目录的所有文件和目录包括子目录与子文件

    func RemoveAll(path string) error

7、刷新

  • 作用:将内存的中的数据落到磁盘中。
func main() {
  create, err := os.Create("test.txt")
  if err != nil {
    panic(err)
  }
  defer create.Close()

  _, err = create.Write([]byte("hello"))
  if err != nil {
    panic(err)
  }

    // 刷盘
  if err := create.Sync();err != nil {
    return
  }
}

8、文件夹

8.1 文件夹读取

  • os.ReadDir

    • func ReadDir(name string) ([]DirEntry, error)

      func main() {
         // 当前目录
         dir, err := os.ReadDir(".")
         if err != nil {
            fmt.Println(err)
         } else {
            for _, entry := range dir {
               fmt.Println(entry.Name())
            }
         }
      }
      
  • *os.File.ReadDir

    • func (f *File) ReadDir(n int) ([]DirEntry, error) // n < 0时,则读取文件夹下所有的内容
      func main() {
         // 当前目录
         dir, err := os.Open(".")
         if err != nil {
            fmt.Println(err)
         }
         defer dir.Close()
         dirs, err := dir.ReadDir(-1)
         if err != nil {
            fmt.Println(err)
         } else {
            for _, entry := range dirs {
               fmt.Println(entry.Name())
            }
         }
      }
      

8.2 文件夹创建

  • func Mkdir(name string, perm FileMode) error // 用指定的权限创建指定名称的目录
  • func MkdirAll(path string, perm FileMode) error // 相较于前者该函数会创建一切必要的父目录
  • 实例
    func main() {
      err := os.Mkdir("src", 0666)
      if err != nil {
        fmt.Println(err)
      } else {
        fmt.Println("创建成功")
      }
    }
    

8.3 文件夹复制

  • 使用filepath标准库
func CopyDir(src, dst string) error {
    // 检查源文件夹的状态
  _, err := os.Stat(src)
  if err != nil {
    return err
  }

  return filepath.Walk(src, func(path string, info fs.FileInfo, err error) error {
    if err != nil {
      return err
    }

        // 计算相对路径
    rel, err := filepath.Rel(src, path)
    if err != nil {
      return err
    }

        // 拼接目标路径
    destpath := filepath.Join(dst, rel)

        // 创建文件夹
    var dirpath string
    var mode os.FileMode = 0755
    if info.IsDir() {
      dirpath = destpath
      mode = info.Mode()
    } else if info.Mode().IsRegular() {
      dirpath = filepath.Dir(destpath)
    }

    if err := os.MkdirAll(dirpath, mode); err != nil {
      return err
    }

        // 创建文件
    if info.Mode().IsRegular() {
      srcfile, err := os.Open(path)
      if err != nil {
        return err
      }
            // 一定要记得关闭文件
      defer srcfile.Close()
      destfile, err := os.OpenFile(destpath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, info.Mode())
      if err != nil {
        return err
      }
      defer destfile.Close()

            // 复制文件内容
      if _, err := io.Copy(destfile, srcfile); err != nil {
        return err
      }
      return nil
    }

    return nil
  })
}