美文网首页
【go语言学习】文件操作file

【go语言学习】文件操作file

作者: Every_dawn | 来源:发表于2020-10-06 09:36 被阅读0次

    一、File文件操作

    file类是在os包中的,封装了底层的文件描述符和相关信息,同时封装了Read和Write的实现。

    1、FileInfo接口

    FileInfo接口中定义了File信息相关的方法。

    go源码:

    // os包的Stat方法返回FileInfo接口
    func Stat(name string) (FileInfo, error) {}
    
    // FileInfo接口提供了获得文件信息的方法
    type FileInfo interface {
        Name() string       // base name of the file 文件名.扩展名 aa.txt
        Size() int64        // 文件大小,字节数 12540
        Mode() FileMode     // 文件权限 -rw-rw-rw-
        ModTime() time.Time // 修改时间 2018-04-13 16:30:53 +0800 CST
        IsDir() bool        // 是否文件夹
        Sys() interface{}   // 基础数据源接口(can return nil)
    }
    
    2、权限

    至于操作权限perm,除非创建文件时才需要指定,不需要创建新文件时可以将其设定为0。虽然go语言给perm权限设定了很多的常量,但是习惯上也可以直接使用数字,如0666(具体含义和Unix系统的一致)。

    go定义了一个权限常量ModePerm FileMode = 0777,使用os.ModePerm可以获取。

    3、打开模式

    文件打开模式:

    const (
        O_RDONLY int = syscall.O_RDONLY // 只读模式打开文件
        O_WRONLY int = syscall.O_WRONLY // 只写模式打开文件
        O_RDWR   int = syscall.O_RDWR   // 读写模式打开文件
        O_APPEND int = syscall.O_APPEND // 写操作时将数据附加到文件尾部
        O_CREATE int = syscall.O_CREAT  // 如果不存在将创建一个新文件
        O_EXCL   int = syscall.O_EXCL   // 和O_CREATE配合使用,文件必须不存在
        O_SYNC   int = syscall.O_SYNC   // 打开文件用于同步I/O
        O_TRUNC  int = syscall.O_TRUNC  // 如果可能,打开时清空文件
    )
    
    4、文件操作

    go源码:

    package filepath
    
    // IsAbs reports whether the path is absolute.
    func IsAbs(path string) (b bool) {}
    // Abs returns an absolute representation of path.
    func Abs(path string) (string, error) {
        return abs(path)
    }
    
    package os
    
    // File 代表一个打开的文件对象
    type File struct {
        *file // os specific
    }
    // Open打开一个文件用于读取。
    // 如果操作成功,返回的文件对象的方法可用于读取数据;对应的文件描述符具有O_RDONLY模式。
    // 如果出错,错误底层类型是*PathError。
    func Open(name string) (*File, error) {
        return OpenFile(name, O_RDONLY, 0)
    }
    // OpenFile是一个更一般性的文件打开函数,大多数调用者都应用Open或Create代替本函数。
    // 它会使用指定的选项(如O_RDONLY等)、指定的模式(如0666等)打开指定名称的文件。
    // 如果操作成功,返回的文件对象可用于I/O。如果出错,错误底层类型是*PathError。
    func OpenFile(name string, flag int, perm FileMode) (*File, error) {}
    // Create采用模式0666(任何人都可读写,不可执行)创建一个名为name的文件,如果文件已存在会截断它(为空文件)。
    // 如果成功,返回的文件对象可用于I/O;对应的文件描述符具有O_RDWR模式。如果出错,错误底层类型是*PathError。
    func Create(name string) (*File, error) {
        return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
    }
    // 创建文件夹,如果文件夹存在,创建失败
    func Mkdir(name string, perm FileMode) error {}
    // os.MkDirAll(),可以创建多层
    func MkdirAll(path string, perm FileMode) error {}
    // 删除文件或目录:慎用,慎用,再慎用
    func Remove(name string) error {}
    // 删除所有
    func RemoveAll(path string) error {}
    
    //Name方法返回文件名称。
    func (f *File) Name() string { return f.name }
    //Stat返回描述文件f的FileInfo类型值。如果出错,错误底层类型是*PathError。
    func (file *File) Stat() (FileInfo, error) {}
    // Read reads up to len(b) bytes from the File.
    // It returns the number of bytes read and any error encountered.
    // At end of file, Read returns 0, io.EOF.
    func (f *File) Read(b []byte) (n int, err error) {}
    // Write writes len(b) bytes to the File.
    // It returns the number of bytes written and an error, if any.
    // Write returns a non-nil error when n != len(b).
    func (f *File) Write(b []byte) (n int, err error) {}
    // Close关闭文件f,使文件不能用于读写。如果文件已经关闭返回错误:file already closed。
    func (f *File) Close() error {}
    

    二、i/o操作

    I/O操作也叫输入输出操作。其中I是指Input,O是指Output,用于读或者写数据的,有些语言中也叫流操作,是指数据通信的通道。

    1、i/o包

    io包中提供I/O原始操作的一系列接口。它主要包装了一些已有的实现,如 os 包中的那些,并将这些抽象成为实用性的功能和一些其他相关的接口。

    在io包中最重要的是两个接口:Reader和Writer接口

    • Reader接口的定义,Read()方法用于读取数据。
    type Reader interface {
            Read(p []byte) (n int, err error)
    }
    

    示例代码:

    package main
    
    import (
        "fmt"
        "io"
        "os"
    )
    
    func main() {
        filePath := "aa.txt"
        // 1.打开文件
        f, err := os.Open(filePath)
        if err != nil {
            fmt.Println("err:", err)
            return
        }
        // 2.延迟关闭文件
        defer f.Close()
        // 3.读取文件
        // 3.1 创建一个byte类型的切片,用于存储读到的数据
        temp := make([]byte, 4)
        // 3.2 循环读取文件内容
        for {
            n, err := f.Read(temp)
            if n == 0 || err == io.EOF {
                fmt.Println("文件读取完了")
                return
            }
            if err != nil && err != io.EOF {
                fmt.Println("err: ", err)
                return
            }
            fmt.Printf("读取到了%d个字节.\n", n)
            fmt.Println(string(temp[:n]))
        }
    }
    

    运行结果

    读取到了4个字节.
    hell
    读取到了4个字节.
    o wo
    读取到了3个字节.
    rld
    文件读取完了
    
    • Writer接口的定义,Write()方法用于写出数据。
    type Writer interface {
            Write(p []byte) (n int, err error)
    }
    

    示例代码

    package main
    
    import (
        "fmt"
        "os"
    )
    
    func main() {
        filePath := "ab.txt"
        // 1.打开文件(以可读可写的方式)
        f, err := os.OpenFile(filePath, os.O_CREATE|os.O_RDWR, os.ModePerm)
        if err != nil {
            fmt.Println("err: ", err)
            return
        }
        // 2.延迟关闭文件
        defer f.Close()
        // 3.写入内容文件
        n, err := f.Write([]byte("hello world"))
        if err != nil {
            fmt.Println("err: ", err)
            return
        }
        fmt.Printf("写入%d个字节\n", n)
        n, err = f.WriteString("你好 世界")
        if err != nil {
            fmt.Println("err: ", err)
            return
        }
        fmt.Printf("写入%d个字节\n", n)
    }
    

    运行结果

    写入11个字节
    写入13个字节
    

    三、bufio包

    Go语言在io操作中,还提供了一个bufio的包,使用这个包可以大幅提高文件读写的效率。

    1、bufio包原理

    bufio 是通过缓冲来提高效率。

    io操作本身的效率并不低,低的是频繁的访问本地磁盘的文件。所以bufio就提供了缓冲区(分配一块内存),读和写都先在缓冲区中,最后再读写文件,来降低访问本地磁盘的次数,从而提高效率。

    bufio 封装了io.Reader或io.Writer接口对象,并创建另一个也实现了该接口的对象。

    io.Reader或io.Writer 接口实现read() 和 write() 方法,对于实现这个接口的对象都是可以使用这两个方法的。

    Reader对象

    bufio.Reader 是bufio中对io.Reader 的封装

    // Reader implements buffering for an io.Reader object.
    type Reader struct {
        buf          []byte
        rd           io.Reader // reader provided by the client
        r, w         int       // buf read and write positions
        err          error
        lastByte     int // last byte read for UnreadByte; -1 means invalid
        lastRuneSize int // size of last rune read for UnreadRune; -1 means invalid
    }
    

    bufio.Read(p []byte) 相当于读取大小len(p)的内容,思路如下:

    1. 当缓存区有内容的时,将缓存区内容全部填入p并清空缓存区
    2. 当缓存区没有内容的时候且len(p)>len(buf),即要读取的内容比缓存区还要大,直接去文件读取即可
    3. 当缓存区没有内容的时候且len(p)<len(buf),即要读取的内容比缓存区小,缓存区从文件读取内容充满缓存区,并将p填满(此时缓存区有剩余内容)
    4. 以后再次读取时缓存区有内容,将缓存区内容全部填入p并清空缓存区(此时和情况1一样)

    Writer对象

    bufio.Writer 是bufio中对io.Writer 的封装

    // Writer implements buffering for an io.Writer object.
    // If an error occurs writing to a Writer, no more data will be
    // accepted and all subsequent writes, and Flush, will return the error.
    // After all data has been written, the client should call the
    // Flush method to guarantee all data has been forwarded to
    // the underlying io.Writer.
    type Writer struct {
        err error
        buf []byte
        n   int
        wr  io.Writer
    }
    

    bufio.Write(p []byte) 的思路如下

    1. 判断buf中可用容量是否可以放下 p
    2. 如果能放下,直接把p拼接到buf后面,即把内容放到缓冲区
    3. 如果缓冲区的可用容量不足以放下,且此时缓冲区是空的,直接把p写入文件即可
    4. 如果缓冲区的可用容量不足以放下,且此时缓冲区有内容,则用p把缓冲区填满,把缓冲区所有内容写入文件,并清空缓冲区
    5. 判断p的剩余内容大小能否放到缓冲区,如果能放下(此时和步骤1情况一样)则把内容放到缓冲区
    6. 如果p的剩余内容依旧大于缓冲区,(注意此时缓冲区是空的,情况和步骤3一样)则把p的剩余内容直接写入文件。
    2、bufio包

    bufio包实现了有缓冲的I/O。它包装一个io.Reader或io.Writer接口对象,创建另一个也实现了该接口,且同时还提供了缓冲和一些文本I/O的帮助函数的对象。

    • bufie.Reader:
    // NewReaderSize 将 rd 封装成一个带缓存的 bufio.Reader 对象,
    // 缓存大小由 size 指定(如果小于 16 则会被设置为 16)。
    // 如果 rd 的基类型就是有足够缓存的 bufio.Reader 类型,则直接将
    // rd 转换为基类型返回。
    func NewReaderSize(rd io.Reader, size int) *Reader{}
    
    // NewReader 相当于 NewReaderSize(rd, 4096)
    func NewReader(rd io.Reader) *Reader {
        return NewReaderSize(rd, defaultBufSize)
    }
    
    // Read 从 b 中读出数据到 p 中,返回读出的字节数和遇到的错误。
    func (b *Reader) Read(p []byte) (n int, err error) {}
    
    // ReadByte读取并返回一个字节.
    // 如果没有可用的字节,则返回一个错误.
    func (b *Reader) ReadByte() (byte, error) {}
    
    // ReadBytes,读取直到第一次出现分隔符,返回读取到的字节切片。
    func (b *Reader) ReadBytes(delim byte) ([]byte, error) {}
    
    // ReadString 功能同 ReadBytes,只不过返回的是字符串。
    func (b *Reader) ReadString(delim byte) (string, error) {}
    

    示例代码:

    package main
    
    import (
        "bufio"
        "fmt"
        "io"
        "os"
    )
    
    func main() {
        filePath := "aa.txt"
        // 1.打开文件
        f, err := os.Open(filePath)
        if err != nil {
            fmt.Println("err: ", err)
            return
        }
        // 2.延迟关闭文件
        defer f.Close()
        // 3.读取内容
        r := bufio.NewReader(f)
        for {
            s, err := r.ReadString('\n')
            if err == io.EOF {
                // 判断s中是否有内容需要输出。
                if len(s) != 0 {
                    fmt.Println(s)
                }
                fmt.Println("读取完毕")
                break
            }
            if err != nil && err != io.EOF {
                fmt.Println("err: ", err)
                return
            }
            fmt.Println(s)
        }
    }
    

    输出结果

    两岸舟船各背驰,
    
    波痕交涉亦难为。
    
    只余鸥鹭无拘管,
    
    北去南来自在飞。
    读取完毕
    
    • bufio.Writer:
    // NewWriterSize 将 wr 封装成一个带缓存的 bufio.Writer 对象,
    // 缓存大小由 size 指定(如果小于 4096 则会被设置为 4096)。
    // 如果 wr 的基类型就是有足够缓存的 bufio.Writer 类型,则直接将
    // wr 转换为基类型返回。
    func NewWriterSize(wr io.Writer, size int) *Writer{}
    
    // NewWriter 相当于 NewWriterSize(wr, 4096)
    func NewWriter(wr io.Writer) *Writer{}
    
    // WriteString 功能同 Write,只不过写入的是字符串
    func (b *Writer) WriteString(s string) (int, error){}
    
    // Flush 将缓存中的数据提交到底层的 io.Writer 中
    func (b *Writer) Flush() error{}
    

    示例代码:

    package main
    
    import (
        "bufio"
        "fmt"
        "os"
    )
    
    func main() {
        filePath := "ab.txt"
        // 1.打开文件
        f, err := os.OpenFile(filePath, os.O_CREATE|os.O_TRUNC|os.O_RDWR, os.ModePerm)
        if err != nil {
            fmt.Println("err: ", err)
            return
        }
        // 2.延迟关闭文件
        defer f.Close()
        // 3.写入内容
        w := bufio.NewWriter(f)
        n, err := w.WriteString("hello world")
        if err != nil {
            fmt.Println("err: ", err)
            return
        }
        fmt.Printf("写入了%d个字节\n", n)
    }
    

    运行结果

    写入了11个字节
    

    四、ioutil包

    除了io包可以读写数据,Go语言中还提供了一个辅助的工具包就是ioutil,里面的方法虽然不多,但是都还蛮好用的。

    该包的介绍只有一句话:Package ioutil implements some I/O utility functions。

    ioutil包的方法:

    // ReadAll 读取 r 中的所有数据,返回读取的数据和遇到的错误。
    // 如果读取成功,则 err 返回 nil,而不是 EOF,因为 ReadAll 定义为读取
    // 所有数据,所以不会把 EOF 当做错误处理。
    func ReadAll(r io.Reader) ([]byte, error){}
    
    // ReadFile 读取文件中的所有数据,返回读取的数据和遇到的错误。
    // 如果读取成功,则 err 返回 nil,而不是 EOF
    func ReadFile(filename string) ([]byte, error){}
    
    // WriteFile 向文件中写入数据,写入前会清空文件。
    // 如果文件不存在,则会以指定的权限创建该文件。
    // 返回遇到的错误。
    func WriteFile(filename string, data []byte, perm os.FileMode) error{}
    
    // ReadDir 读取指定目录中的所有目录和文件(不包括子目录)。
    // 返回读取到的文件信息列表和遇到的错误,列表是经过排序的。
    func ReadDir(dirname string) ([]os.FileInfo, error){}
    
    // TempFile 在 dir 目录中创建一个以 prefix 为前缀的临时文件,并将其以读
    // 写模式打开。返回创建的文件对象和遇到的错误。
    // 如果 dir 为空,则在默认的临时目录中创建文件(参见 os.TempDir),多次
    // 调用会创建不同的临时文件,调用者可以通过 f.Name() 获取文件的完整路径。
    // 调用本函数所创建的临时文件,应该由调用者自己删除。
    func TempFile(dir, prefix string) (f *os.File, err error){}
    
    // TempDir 功能同 TempFile,只不过创建的是目录,返回目录的完整路径。
    func TempDir(dir, prefix string) (name string, err error){}
    

    示例代码:

    package main
    
    import (
        "fmt"
        "io/ioutil"
        "os"
        "strings"
    )
    
    func main() {
        filePath := "aa.txt"
        // 1.ReadFile
        data, err := ioutil.ReadFile(filePath)
        handleError(err)
        fmt.Println(string(data))
        // 2.ReadAll
        s := "相濡以沫,不如相忘于江湖"
        r := strings.NewReader(s)
        data, err = ioutil.ReadAll(r)
        handleError(err)
        fmt.Println(string(data))
        // 3.ReadDir
        dirName := "E:/golang/go_project"
        fileInfoData, err := ioutil.ReadDir(dirName)
        handleError(err)
        for k, v := range fileInfoData {
            fmt.Printf("第%d个文件名称是%s, 是目录吗?%v\n", k, v.Name(), v.IsDir())
        }
        // 4.WriteFile
        filePath = "cc.txt"
        err = ioutil.WriteFile(filePath, []byte("相见亦难别亦难,东风无力百花残"), os.ModePerm)
        handleError(err)
        // 5.TempFile
        f, err := ioutil.TempFile(dirName, "test")
        handleError(err)
        fmt.Println(f)
        n, err := f.WriteString("这是一种的临时测试文件")
        handleError(err)
        fmt.Println("写入字符个数:", n)
        // 6.TempDir
        name, err := ioutil.TempDir(dirName, "test")
        handleError(err)
        fmt.Println(name)
    }
    
    func handleError(err error) {
        if err != nil {
            fmt.Println("err: ", err)
            return
        }
    }
    

    运行结果

    两岸舟船各背驰,
    波痕交涉亦难为。
    只余鸥鹭无拘管,
    北去南来自在飞。
    相濡以沫,不如相忘于江湖
    第0个文件名称是aa.txt, 是目录吗?false
    第1个文件名称是ab.txt, 是目录吗?false
    第2个文件名称是bb.txt, 是目录吗?false
    第3个文件名称是cc.txt, 是目录吗?false
    第4个文件名称是go.mod, 是目录吗?false
    第5个文件名称是main, 是目录吗?true
    第6个文件名称是main.go, 是目录吗?false
    第7个文件名称是model, 是目录吗?true
    第8个文件名称是test523723675, 是目录吗?false
    &{0xc00007b180}
    写入字符个数: 33
    E:\golang\go_project\test800637258
    

    五、文件复制

    • 第一种:io包下Read()和Write()直接读写:我们自己创建读取数据的切片的大小,直接影响性能。
    • 第二种:ioutil包
    • 第三种:io包下Copy()方法:
    package main
    
    import (
        "fmt"
        "io"
        "io/ioutil"
        "os"
    )
    
    func main() {
        srcFile := "aa.txt"
        distFile := "bb.txt"
        // n, err := copyFile1(srcFile, distFile)
        // n, err := copyFile2(srcFile, distFile)
        n, err := copyFile3(srcFile, distFile)
        if err != nil {
            fmt.Println("err: ", err)
            return
        }
        fmt.Println("拷贝的字节数:", n)
    }
    
    // copyFile1 io包的Read和Write方法
    func copyFile1(srcFile, distFile string) (n int, err error) {
        srcfile, err := os.Open(srcFile)
        if err != nil {
            return 0, err
        }
        distfile, err := os.OpenFile(distFile, os.O_CREATE|os.O_TRUNC|os.O_RDWR, os.ModePerm)
        if err != nil {
            return 0, err
        }
        defer srcfile.Close()
        defer distfile.Close()
    
        total := 0
        temp := make([]byte, 1024)
        for {
            n, err = srcfile.Read(temp)
            if err == io.EOF || n == 0 {
                fmt.Println("拷贝完毕")
                break
            } else if err != nil && err != io.EOF {
                return total, err
            }
            total += n
            _, err = distfile.Write(temp[:n])
        }
        return total, nil
    }
    
    // copyFile2 ioutil包的方法
    func copyFile2(srcFile, distFile string) (n int, err error) {
        data, err := ioutil.ReadFile(srcFile)
        if err != nil {
            return 0, err
        }
        err = ioutil.WriteFile(distFile, data, os.ModePerm)
        if err != nil {
            return 0, err
        }
        return len(data), nil
    }
    
    // copyFile3 io包的copy方法
    func copyFile3(srcFile, distFile string) (n int, err error) {
        srcfile, err := os.Open(srcFile)
        if err != nil {
            return 0, err
        }
        distfile, err := os.OpenFile(distFile, os.O_CREATE|os.O_TRUNC|os.O_RDWR, os.ModePerm)
        if err != nil {
            return 0, err
        }
        defer srcfile.Close()
        defer distfile.Close()
        total, err := io.Copy(distfile, srcfile)
        return int(total), err
    }
    

    六、断点续传

    1、Seeker接口

    Seeker是包装基本Seek方法的接口。

    type Seeker interface {
            Seek(offset int64, whence int) (int64, error)
    }
    

    seek(offset, whence),设置指针光标的位置,随机读写文件:

    第一个参数:偏移量
    第二个参数:如何设置          
    
                0:seekStart表示相对于文件开始,
                1:seekCurrent表示相对于当前偏移量,
                2:seek end表示相对于结束。
    
    const (
        SEEK_SET int = 0 // seek relative to the origin of the file
        SEEK_CUR int = 1 // seek relative to the current offset
        SEEK_END int = 2 // seek relative to the end
    )
    

    示例代码:

    package main
    
    import (
        "fmt"
        "os"
    )
    
    func main() {
        filePath := "aa.txt" // ABCDEFGH
        temp := make([]byte, 1)
        f, _ := os.Open(filePath)
        f.Seek(2, os.SEEK_SET)
        f.Read(temp)
        fmt.Println(string(temp[:])) // C
    }
    
    2、断点续传实现

    首先思考几个问题
    Q1:如果你要传的文件比较大,那么是否有方法可以缩短耗时?
    Q2:如果在文件传递过程中,程序因各种原因被迫中断了,那么下次再重启时,文件是否还需要重头开始?
    Q3:传递文件的时候,支持暂停和恢复么?即使这两个操作分布在程序进程被杀前后。

    通过断点续传可以实现,不同的语言有不同的实现方式。我们看看Go语言中,通过Seek()方法如何实现:

    先说一下思路:想实现断点续传,主要就是记住上一次已经传递了多少数据,那我们可以创建一个临时文件,记录已经传递的数据量,当恢复传递的时候,先从临时文件中读取上次已经传递的数据量,然后通过Seek()方法,设置到该读和该写的位置,再继续传递数据。

    示例代码:

    package main
    
    import (
        "fmt"
        "io"
        "os"
        "strconv"
    )
    
    func main() {
        srcFile := "06.jpg"
        distFile := "美女.jpg"
        tempFile := "temp.txt"
        tempfile, err := os.OpenFile(tempFile, os.O_CREATE|os.O_RDWR, os.ModePerm)
        handleError(err)
        srcfile, err := os.Open(srcFile)
        handleError(err)
        distfile, err := os.OpenFile(distFile, os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)
        handleError(err)
        defer srcfile.Close()
        defer distfile.Close()
        // defer tempfile.Close()
    
        // 1.读取临时文件夹的内容,获得已拷贝的字节数
        _, err = tempfile.Seek(0, io.SeekStart)
        handleError(err)
        temp := make([]byte, 1024*4)
        n1, err := tempfile.Read(temp)
        handleError(err)
        count, err := strconv.Atoi(string(temp[:n1]))
        handleError(err)
        // 2.设置读写偏移量
        _, err = srcfile.Seek(int64(count), os.SEEK_SET)
        handleError(err)
        _, err = distfile.Seek(int64(count), os.SEEK_SET)
        handleError(err)
        data := make([]byte, 1024*4)
        total := int(count)
        // 3.循环读写数据
        for {
            n2, err := srcfile.Read(data)
            if n2 == 0 || err == io.EOF {
                fmt.Println("文件读完了")
                tempfile.Close()
                os.Remove(tempFile)
                break
            }
            // 写入数据
            n3, err := distfile.Write(data[:n2])
            // 将写入量写入临时文件中
            total += n3
            _, err = tempfile.Seek(0, os.SEEK_SET)
            handleError(err)
            _, err = tempfile.WriteString(strconv.Itoa(total))
            handleError(err)
            // 模拟程序中断
            if total > 2000 {
                panic("崩了")
            }
        }
    
    }
    
    func handleError(err error) {
        if err != nil {
            fmt.Println("err: ", err)
            return
        }
    }
    

    运行结果

    运行5次程序实现图片的拷贝
    

    相关文章

      网友评论

          本文标题:【go语言学习】文件操作file

          本文链接:https://www.haomeiwen.com/subject/xdrwuktx.html