前言

哈喽,大家好,我是asong

咱们都晓得在Unix中万物都被称为文件,文件解决是一个十分常见的问题,所以本文就总结了Go语言操作文件的常见形式,整体思路如下:

Go语言版本:1.18

本文所有代码曾经上传github:https://github.com/asong2020/...

操作文件包含哪些

操作一个文件离不开这几个动作:

  • 创立文件
  • 关上文件
  • 读取文件
  • 写入文件
  • 敞开文件
  • 打包/解包
  • 压缩/解压缩
  • 扭转文件权限
  • 删除文件
  • 挪动文件
  • 重命名文件
  • 清空文件

所以本文就针对这些操作总结了一些示例办法供大家参考;

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

Go语言官网库:osio/ioutilbufio涵盖了文件操作的所有场景,

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)    }}

写文件

快写文件

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}

按行写文件

osbuffo写数据都没有提供按行写入的办法,所以咱们能够在调用os.WriteStringbufio.WriteString办法是在数据中退出换行符即可,来看示例:

import (    "bufio"    "log"    "os")// 间接操作IOfunc 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}

偏移量写入

某些场景咱们想依据给定的偏移量写入数据,能够应用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}

缓存区写入

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}

读文件

读取全文件

有两种形式咱们能够读取全文件:

  • osio/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}

逐行读取

os库中提供了Read办法是依照字节长度读取,如果咱们想要按行读取文件须要配合bufio一起应用,bufio中提供了三种办法ReadLineReadBytes("\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}

按块读取文件

有些场景咱们想依照字节长度读取文件,这时咱们能够如下办法:

  • os库的Read办法
  • os库配合bufio.NewReader调用Read办法
  • os库配合io库的ReadFullReadAtLeast办法
// use bufio.NewReaderfunc 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 osfunc 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.ReadAtLeastfunc 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}

分隔符读取

bufio包中提供了Scanner扫描器模块,它的次要作用是把数据流宰割成一个个标记并除去它们之间的空格,他反对咱们定制Split函数做为分隔函数,分隔符能够不是一个简略的字节或者字符,咱们能够自定义分隔函数,在分隔函数实现分隔规定以及指针挪动多少,返回什么数据,如果没有定制Split函数,那么就会应用默认ScanLines作为分隔函数,也就是应用换行作为分隔符,bufio中还提供了默认办法ScanRunesScanWrods,上面咱们用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}

打包/解包

Go语言的archive包中提供了tarzip两种打包/解包办法,这里以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()    }}

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)    }}

总结

本文归根结底是介绍osiobufio这些包如何操作文件,因为Go语言操作提供了太多了办法,借着本文全都介绍进去,在应用的时候能够很不便的当作文档查问,如果你问用什么办法操作文件是最优的办法,这个我也没法答复你,须要依据具体场景剖析的,如果这些办法你都晓得了,在写一个benchmark比照一下就能够了,实际才是测验真谛的唯一标准。

本文所有代码曾经上传github:https://github.com/asong2020/...

好啦,本文到这里就完结了,我是asong,咱们下期见。

欢送关注公众号:Golang梦工厂