• Go 语言标准库之 io & io/ioutil 包


    io 包提供了对I/O原语的基本接口,其基本任务是包装这些原语已有的实现(如 os 包里的原语),使之成为共享的公共接口,这些公共接口抽象出了泛用的函数并附加一些相关的原语的操作。

    io 包常用接口

    io.Reader 和 io.Writer 接口

    io.Reader 接口

    // io.Reader 接口用于包装基本的读取方法
    type Reader interface {
        Read(p []byte) (n int, err error)
    }
    

    从底层输入流读取最多len(p)个字节数据写入到 p 中,返回读取的字节数 n 和遇到的任何错误 err。即使 Read 方法返回的n < len(p),在调用过程也会占用len(p)个字节作为暂存空间。若可读取的数据不足len(p)个字节,Read 方法会返回可用数据,而不是等待更多数据。

    当 Read 方法读取n > 0个字节后遇到一个错误或 EOF (end-of-file),会返回读取的字节数,同时在该次调用返回一个非 nil 错误或者在下一次的调用时返回 0 和该错误。一般情况下,io.Reader接口会在输入流的结尾返回非 0 的字节数,返回值 err 为 nil 或者io.EOF,但下次调用必然返回(0, io.EOF)。调用者在考虑错误之前应当首先处理n > 0字节的返回数据,这样做可以正确地处理在读取一些字节后产生的I/O错误,同时允许 EOF 的出现。

    ☕️ 示例代码

    package main
    
    import (
        "fmt"
        "io"
        "os"
        "strings"
    )
    
    func ReadFrom(reader io.Reader, num int) ([]byte, error) {
        p := make([]byte, num)
        n, err := reader.Read(p)
        if n > 0 {
            return p[:n], nil
        }
        return p, err
    }
    
    func main() {
        // 例子 1:从标准输入中读取 11 个字节
        // 控制台输入:hello world!!!!
        data, err := ReadFrom(os.Stdin, 11)
        if err != nil {
            panic(err)
        }
        fmt.Printf("%s\n", data) // hello world
    
        // 例子 2:从普通文件中读取 9 个字节
        // 文件内容:hello world!!!!
        f, err := os.Open("./test.txt")
        if err != nil {
            panic(err)
        }
        defer f.Close()
        data, err = ReadFrom(f, 9)
        if err != nil {
            panic(err)
        }
        fmt.Printf("%s\n", data) // hello wor
    
        // 例子 3:从字符串中读取 12 个字节
        r := strings.NewReader("hello world!!!!")
        data, err = ReadFrom(r, 12)
        if err != nil {
            panic(err)
        }
        fmt.Printf("%s\n", data) // hello world!
    }
    

    io.Writer 接口

    // io.Writer 接口用于包装基本的写入方法
    type Writer interface {
        Write(p []byte) (n int, err error)
    }
    

    len(p) 个字节数据从 p 中写入底层的输出流,返回写入的字节数 n 和遇到的任何错误 err。如果 Write 方法返回的n < len(p),它就必须返回一个 非 nil 的错误。Write 方法不能修改切片 p 中的数据,即使临时修改也不行。

    ⭐️ 示例代码

    package main
    
    import (
        "fmt"
        "io"
        "os"
    )
    
    func WriteTo(write io.Writer, p []byte) (int, error) {
        n, err := write.Write(p)
        return n, err
    }
    
    func main() {
        p := []byte("hello world!!!\n")
    
        // 例子 1:将字符串写入到标准输出
        n, err := WriteTo(os.Stdout, p)
        if err != nil {
            panic(err)
        }
        fmt.Printf("write %d bytes\n", n)
    
        // 例子 2:将字符串写入文件中
        f, err := os.Create("./test.txt")
        if err != nil {
            panic(err)
        }
        defer f.Close()
        n, err = WriteTo(f, p)
        if err != nil {
            panic(err)
        }
        fmt.Printf("write %d bytes\n", n)
    }
    
    // 控制台输出:
    // hello world!!!
    // write 15 bytes
    // write 15 bytes
    
    // test.txt 文件内容:
    // hello world!!!
    

    io.Reader 和 io.Writer 接口实现类型

    • os.File同时实现了io.Readerio.Writer接口
    • strings.Reader实现了io.Reader接口
    • bufio.Readerbufio.Writer 分别实现了io.Readerio.Writer接口
    • bytes.Buffer同时实现了io.Readerio.Writer接口
    • bytes.Reader实现了io.Reader接口
    • compress/gzip.Readercompress/gzip.Writer分别实现了io.Readerio.Writer接口
    • crypto/cipher.StreamReadercrypto/cipher.StreamWriter分别实现了io.Readerio.Writer接口
    • crypto/tls.Conn同时实现了io.Readerio.Writer接口
    • encoding/csv.Readerencoding/csv.Writer分别实现了io.Readerio.Writer接口
    • mime/multipart.Part 实现了io.Reader接口
    • net/conn分别实现了io.Readerio.Writer接口(Conn 接口定义了 Read 和 Write 方法)

    除此之外,io 包本身也有这两个接口的实现类型。如:

    • 实现了io.Reader的类型:LimitedReaderPipeReaderSectionReader
    • 实现了io.Writer的类型:PipeWriter

    以上类型中,常用的类型有:os.Filestrings.Readerbufio.Reader/Writerbytes.Bufferbytes.Reader


    io.ReaderAt 和 io.WriterAt 接口

    io.ReaderAt 接口

    // io.ReaderAt 接口包装了基本的 ReadAt 方法
    type ReaderAt interface {
        ReadAt(p []byte, off int64) (n int, err error)
    }
    

    从底层输入流的偏移量 off 位置读取最多len(p)字节数据写入 p,返回读取的字节数 n 和遇到的任何错误 err。当ReadAt方法返回的n < len(p)时,它会返回一个非 nil 的错误来说明没有读取更多的字节的原因。在这方面,ReadAt方法是比 Read 方法要严格的。

    即使ReadAt方法返回的n < len(p),在调用过程也会占用len(p)个字节作为暂存空间。如果有部分可用数据,但不够len(p)字节,ReadAt 方法会阻塞直到获取len(p)个字节数据或者遇到错误。在这方面,ReadAt方法和 Read 方法是不同的。如果 ReadAt 方法返回时到达输入流的结尾,返回的n == len(p),返回的 err 为 nil 或者io.EOF

    如果ReadAt方法是从某个有偏移量的底层输入流读取,ReadAt方法既不应影响底层的偏移量,也不被它所影响。ReadAt方法的调用者可以对同一输入流执行并行的ReadAt调用。

    ✏️ 示例代码

    package main
    
    import (
        "fmt"
        "strings"
    )
    
    func main() {
        reader := strings.NewReader("Go语言中文网")
        p := make([]byte, 6)
        n, err := reader.ReadAt(p, 2)
        if err != nil {
            panic(err)
        }
        fmt.Printf("Read %d bytes: %s\n", n, p) // Read 6 bytes: 语言
    }
    

    io.WriterAt 接口

    // io.WriterAt 接口包装了基本的 WriteAt 方法
    type WriterAt interface {
        WriteAt(p []byte, off int64) (n int, err error)
    }
    

    len(p)个字节数据从 p 中写入偏移量 off 位置的底层输出流中,返回写入的字节数 n 和遇到的任何错误 err。当WriteAt方法返回的n < len(p)时,它就必须返回一个非 nil 的错误。

    如果WriteAt方法写入的目标是某个有偏移量的底层输出流,WriteAt方法既不应影响底层的偏移量,也不被它所影响。ReadAt方法的调用者可以对同一输入流执行并行的WriteAt调用。(前提是写入范围不重叠)

    示例代码

    package main
    
    import (
        "fmt"
        "os"
    )
    
    func main() {
        file, err := os.Create("writeAt.txt")
        if err != nil {
            panic(err)
        }
        defer file.Close()
        file.WriteString("Golang中文社区——这里是多余hh!!!")
        n, err := file.WriteAt([]byte("Go语言中文网"), 24)
        if err != nil {
            panic(err)
        }
        fmt.Printf("write %d bytes", n) // write 17 bytes
        file.WriteString("hello world!!!")
    }
    
    // test.txt 文件内容:
    // Golang中文社区——Go语言中文网!!!hello world!!!
    

    io.ReaderFrom 和 io.WriteTo 接口

    io.ReaderFrom 接口

    // io.ReaderFrom 接口包装了基本的 ReadFrom 方法
    type ReaderFrom interface {
        ReadFrom(r Reader) (n int64, err error)
    }
    

    从输入流 r 中读取数据,写入底层输出流,直到 EOF 或发生错误。其返回值 n 为读取的字节数,除io.EOF之外,在读取过程中遇到的其它错误 err 也将被返回。

    注意ReadFrom方法不会返回err == io.EOF

    ✌ 示例代码

    package main
    
    import (
        "bufio"
        "fmt"
        "os"
    )
    
    func main() {
        // test.txt 文件内容:hello world!!!
        file, err := os.Open("./test.txt")
        if err != nil {
            panic(err)
        }
        defer file.Close()
    
        // 调用 file 的读操作从文件中读取文件,并写入标准输出
        writer := bufio.NewWriter(os.Stdout)
        n, err := writer.ReadFrom(file)
        if err != nil {
            panic(err)
        }
        fmt.Printf("read %d bytes\n", n)
        // 将缓存中的数据 flush 到控制台
        writer.Flush()
    }
    
    // 控制台输出:
    // hello world!!!
    // read 15 bytes
    

    io.WriteTo 接口

    // io.WriterTo 接口包装了基本的 WriteTo 方法
    type WriterTo interface {
        WriteTo(w Writer) (n int64, err error)
    }
    

    从底层输入流中读取数据,写入输出流 w 中,直到没有数据可写或发生错误。其返回值 n 为写入的字节数,在写入过程中遇到的任何错误也将被返回。

    ✌ 示例代码

    package main
    
    import (
        "bytes"
        "fmt"
        "os"
    )
    
    func main() {
        // 调用 r 的读操作读取数据,并写入到标准输出
        r := bytes.NewReader([]byte("Go语言中文网\n"))
        n, err := r.WriteTo(os.Stdout)
        if err != nil {
            panic(err)
        }
        fmt.Printf("write %d bytes\n", n)
    }
    
    // 控制台输出:
    // Go语言中文网
    // write 18 bytes
    

    io.Seeker 接口

    // io.Seeker 接口用于包装基本的读/写偏移量移动方法
    type Seeker interface {
        Seek(offset int64, whence int) (int64, error)
    }
    

    设定下一次读/写的位置。offset 为相对偏移量,而 whence 决定相对位置:0 为相对文件开头,1 为相对当前位置,2 为相对文件结尾。返回新的偏移量(相对开头)和可能的错误。

    移动到一个绝对偏移量为负数的位置会导致错误,移动到任何偏移量为正数的位置都是合法的,但其下一次I/O操作的具体行为则要看底层的实现。

    // whence 的值,在 io 包中定义了相应的常量,推荐使用这些常量
    const (
      SeekStart   = 0 // seek relative to the origin of the file
      SeekCurrent = 1 // seek relative to the current offset
      SeekEnd     = 2 // seek relative to the end
    )
    

    ✍ 示例代码

    package main
    
    import (
        "fmt"
        "io"
        "strings"
    )
    
    func main() {
        r := strings.NewReader("Go语言中文网")
        fmt.Println(r.Len()) // 17
        n, err := r.Seek(-6, io.SeekEnd)
        if err != nil {
            panic(err)
        }
        fmt.Println(n) // 11
        c, _, _ := r.ReadRune()
        fmt.Printf("%c\n", c) // 文
    }
    

    io.Closer 接口

    // io.Closer 接口用于包装基本的关闭方法
    type Closer interface {
        Close() error
    }
    

    该接口比较简单,用于关闭数据流。文件 (os.File)、归档(压缩包)、数据库连接、Socket 等需要手动关闭的资源都实现了io.Closer接口。实际编程中,经常将 Close 方法的调用放在 defer 语句中。

    示例代码

    package main
    
    import (
        "os"
    )
    
    func main() {
        file, err := os.Open("./test.txt")
        if err != nil {
            panic(err)
        }
        defer file.Close()
    }
    

    io.ByteReader、io.ByteWriter 和 io.ByteScanner 接口

    // io.ByteReader 接口是基本的 ReadByte 方法的包装
    type ByteReader interface {
        ReadByte() (c byte, err error)
    }
    
    // io.RuneReader 接口是基本的 ReadRune 方法的包装
    type ByteWriter interface {
        WriteByte(c byte) error
    }
    
    // io.ByteScanner 接口在基本的 ReadByte 方法之外还添加了 UnreadByte 方法
    type ByteScanner interface {
        ByteReader
        UnreadByte() error
    }
    

    io.ByteReaderio.ByteWriter接口用于读/写一个字节。在标准库中,有如下类型实现了io.ByteReaderio.ByteWriter接口:

    • bufio.Readerbufio.Writer分别实现了io.ByteReaderio.ByteWriter接口
    • bytes.Buffer同时实现了io.ByteReaderio.ByteWriter接口
    • bytes.Reader实现了io.ByteReader接口
    • strings.Reader实现了io.ByteReader接口

    io.ByteScanner接口内嵌了io.ByteReader接口之外还添加了UnreadByte方法,该方法会还原最近一次读取操作读出的最后一个字节,相当于让读偏移量向前移动一个字节。注意,连续两次UnreadByte方法调用而中间没有任何读取操作,会返回错误。

    ☕️ 示例代码

    package main
    
    import (
        "bytes"
        "fmt"
    )
    
    func main() {
        var ch byte
        fmt.Scanf("%c\n", &ch)
    
        var b bytes.Buffer
    
        // 写入一个字节
        err := b.WriteByte(ch)
        if err != nil {
            panic(err)
        }
        fmt.Println("写入一个字节成功!准备读取该字节……")
    
        // 读取一个字节
        newCh, _ := b.ReadByte()
        fmt.Printf("读取的字节:%c\n", newCh)
    
        // 还原最近一次读取操作读出的最后一个字节
        b.UnreadByte()
        newCh2, _ := b.ReadByte()
        fmt.Printf("再次读取的字节:%c\n", newCh2)
    }
    
    // 控制台输入:
    // A
    // 控制台输出:
    // 写入一个字节成功!准备读取该字节……
    // 读取的字节:A
    // 再次读取的字节:A
    

    io.RuneReader 和 io.RuneScanner 接口

    // io.RuneReader 是基本的 ReadRune 方法的包装
    type RuneReader interface {
        ReadRune() (r rune, size int, err error)
    }
    
    // io.RuneScanner 接口在基本的 ReadRune 方法之外还添加了 UnreadRune 方法
    type RuneScanner interface {
        RuneReader
        UnreadRune() error
    }
    

    io.ByteReader/io.ByteScanner接口类似,不过io.RuneReader/io.RuneScanner接口操作的是一个字符。ReadRune方法读取单个UTF-8编码的字符,返回其 rune 和该字符占用的字节数。如果没有有效的字符,会返回错误。

    UnreadRune方法还原前一次ReadRune操作读取的 unicode 码值,相当于让读偏移量前移一个码值长度。注意,UnreadRune方法调用前必须调用ReadRune方法,UnreadRune方法比UnreadByte严格很多。

    ⭐️ 示例代码

    package main
    
    import (
        "bytes"
        "fmt"
    )
    
    func main() {
        var ch rune
        fmt.Scanf("%c\n", &ch)
    
        var b bytes.Buffer
    
        // 写入一个字符,WriteRune 方法是 bytes.Buffer 自定义的方法
        b.WriteRune(ch)
        fmt.Println("写入一个字符成功!准备读取该字符……")
    
        // 读取一个字符
        newCh, _, _ := b.ReadRune()
        fmt.Printf("读取的字符:%c\n", newCh)
    
        // 还原前一次 ReadRune 操作读取的字符
        b.UnreadRune()
        newCh2, _, _ := b.ReadRune()
        fmt.Printf("再次读取的字符:%c\n", newCh2)
    }
    
    // 控制台输入:
    // 您
    // 控制台输出:
    // 写入一个字符成功!准备读取该字符……
    // 读取的字符:您
    // 再次读取的字符:您
    

    其它聚合接口

    // io.ReadWriter 接口聚合了基本的读写操作
    type ReadWriter interface {
        Reader
        Writer
    }
    
    // io.ReadCloser 接口聚合了基本的读取和关闭操作 
    type ReadCloser interface {
        Reader
        Closer
    }
    
    // io.ReadSeeker 接口聚合了基本的读取和移位操作
    type ReadSeeker interface {
        Reader
        Seeker
    }
    
    // io.ReadWriteCloser 接口聚合了基本的读写和关闭操作
    type ReadWriteCloser interface {
        Reader
        Writer
        Closer
    }
    
    // io.ReadWriteSeeker 接口聚合了基本的读写和移位操作
    type ReadWriteSeeker interface {
        Reader
        Writer
        Seeker
    }
    
    // io.WriteSeeker 接口聚合了基本的写入和移位操作
    type WriteSeeker interface {
        Writer
        Seeker
    }
    
    // io.WriteCloser 接口聚合了基本的写入和关闭操作
    type WriteCloser interface {
        Writer
        Closer
    }
    

    io 包常用函数

    文件复制 io.Copy/io.CopyN

    // 将 src 的数据拷贝到 dst,直到在 src 上到达 EOF 或发生错误。返回拷贝的字节数和遇到的第一个错误
    // 对成功的调用,返回值 err 为 nil 而非 EOF,因为 Copy 定义为从 src 读取直到 EOF,它不会将读取到 EOF视为应报告的错误
    // 如果 src 实现了 WriterTo 接口,会调用 src.WriteTo(dst) 进行拷贝;如果 dst 实现了 ReaderFrom 接口,会调用 dst.ReadFrom(src) 进行拷贝
    func Copy(dst Writer, src Reader) (written int64, err error)
    
    // 从 src 拷贝 n 个字节数据到 dst,直到在 src 上到达 EOF 或发生错误。返回复制的字节数和遇到的第一个错误
    // 只有 err 为 nil 时,written 才会等于 n。如果 dst 实现了 ReaderFrom 接口,本函数会调用它实现拷贝
    func CopyN(dst Writer, src Reader, n int64) (written int64, err error)
    

    ✏️ 示例代码

    package main
    
    import (
        "fmt"
        "io"
        "os"
    )
    
    func main() {
        // 打开原始文件
        originalFile, err := os.Open("./test.txt")
        if err != nil {
            panic(err)
        }
        defer originalFile.Close()
    
        // 创建新的文件作为目标文件
        newFile, err := os.Create("./test_copy.txt")
        if err != nil {
            panic(err)
        }
        defer newFile.Close()
    
        // 从源中复制字节到目标文件
        writtenNum, err := io.Copy(newFile, originalFile)
        if err != nil {
            panic(err)
        }
        fmt.Printf("Copied %d bytes.", writtenNum)
    
        // 将文件内容 flush 到硬盘中
        err = newFile.Sync()
        if err != nil {
            panic(err)
        }
    }
    

    读取至少 N 个字节 io.ReadAtLeast

    // 从 r 至少读取 min 字节数据填充进 buf。函数返回写入的字节数和错误(如果没有读取足够的字节)
    // 只有没有读取到字节时才可能返回 io.EOF;如果读取了有但不够的字节时遇到了 EOF,函数会返回ErrUnexpectedEOF
    // 如果 min 比 buf 的长度还大,函数会返回 ErrShortBuffer。只有返回值 err 为 nil 时,返回值 n 才会不小于 min
    func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)
    

    示例代码

    package main
    
    import (
        "fmt"
        "io"
        "os"
    )
    
    func main() {
        // 打开文件,只读
        file, err := os.Open("./test.txt")
        if err != nil {
            panic(err)
        }
        defer file.Close()
    
        byteSlice := make([]byte, 512)
        minBytes := 8
        // io.ReadAtLeast() 在不能得到最小的字节的时候会返回错误,但已读的文件数据仍保留在 byteSlice 中
        numBytesRead, err := io.ReadAtLeast(file, byteSlice, minBytes)
        if err != nil {
            panic(err)
        }
        fmt.Printf("Number of bytes read: %d\n", numBytesRead)
        fmt.Printf("Data read: %s\n", byteSlice)
    }
    

    读取正好 N 个字节 io.ReadFull

    // 从 r 精确地读取 len(buf) 字节数据填充进 buf。函数返回写入的字节数和错误(如果没有读取足够的字节)
    // 只有没有读取到字节时才可能返回 io.EOF;如果读取了有但不够字节数时遇到了 EOF,函数会返回ErrUnexpectedEOF
    // 只有返回值 err 为 nil 时,返回值 n 才会等于 len(buf)
    func ReadFull(r Reader, buf []byte) (n int, err error)
    

    ✌ 示例代码

    package main
    
    import (
        "fmt"
        "io"
        "os"
    )
    
    func main() {
        // 打开文件,只读
        file, err := os.Open("./test.txt")
        if err != nil {
            panic(err)
        }
        defer file.Close()
    
        // file.Read() 可以读取一个小文件到大的 byte slice 中,
        // 但是 io.ReadFull() 在文件的字节数小于 byte slice 字节数的时候会返回错误
        byteSlice := make([]byte, 2)
        numBytesRead, err := io.ReadFull(file, byteSlice)
        if err != nil {
            panic(err)
        }
        fmt.Printf("Number of bytes read: %d\n", numBytesRead)
        fmt.Printf("Data read: %s\n", byteSlice)
    }
    

    字符串写入 io.WriteString

    // 将字符串 s 的内容写入 w 中,返回写入的字节数和遇到的任何错误。如果 w 已经实现了 WriteString 方法,函数会直接调用该方法
    func WriteString(w Writer, s string) (n int, err error)
    

    ✍ 示例代码

    package main
    
    import (
        "fmt"
        "io"
        "os"
    )
    
    func main() {
        // 打开文件,只读
        file, err := os.Create("./test.txt")
        if err != nil {
            panic(err)
        }
        defer file.Close()
    
        // 写入字符串
        n, err := io.WriteString(file, "hello world!!!!")
        if err != nil {
            panic(err)
        }
        fmt.Printf("Write %d btyes\n", n)
    }
    

    合并多个输入流 io.MultiReader

    // 返回一个将提供的 Readers 在逻辑上串联起来的 Reader 接口,它们依次被读取
    // 当所有的输入流都读取完毕,Read 才会返回 EOF。如果 readers 中任一个返回了非 nil 非 EOF 的错误,Read 方法会返回该错误
    func MultiReader(readers ...Reader) Reader
    

    ✍ 示例代码

    package main
    
    import (
        "bytes"
        "fmt"
        "io"
        "strings"
    )
    
    func main() {
        readers := []io.Reader{
            strings.NewReader("from strings reader!!!"),
            bytes.NewBufferString("from bytes buffer!!!"),
        }
        reader := io.MultiReader(readers...)
        data := make([]byte, 0, 128)
        buf := make([]byte, 10)
    
        for n, err := reader.Read(buf); err != io.EOF; n, err = reader.Read(buf) {
            if err != nil {
                panic(err)
            }
            data = append(data, buf[:n]...)
        }
        fmt.Printf("%s\n", data) // from strings reader!!!from bytes buffer!!!
    }
    

    合并多个输出流 io.MultiWriter

    // 创建一个 Writer 接口,会将提供给其的数据同时写入 Writers 中的所有输出流
    func MultiWriter(writers ...Writer) Writer
    

    示例代码

    package main
    
    import (
        "io"
        "os"
    )
    
    func main() {
        file, err := os.Create("./text.txt")
        if err != nil {
            panic(err)
        }
        defer file.Close()
        writers := []io.Writer{
            file,
            os.Stdout,
        }
        writer := io.MultiWriter(writers...)
    
        // 会同时在 text.txt 文件和控制台中输出:Go语言中文网
        writer.Write([]byte("Go语言中文网"))
    }
    

    读取并自动写入 io.TeeReader

    // 返回一个将其从 r 读取的数据写入 w 的 Reader 接口,所有通过该接口对 r 的读取都会执行对应的对 w 的写入
    // 没有内部的缓冲,写入必须在读取完成前完成,写入时遇到的任何错误都会作为读取错误返回
    func TeeReader(r Reader, w Writer) Reader
    

    ☕️ 示例代码

    package main
    
    import (
        "io"
        "os"
    )
    
    func main() {
        file, err := os.Open("./test.txt")
        if err != nil {
            panic(err)
        }
        defer file.Close()
        r := io.TeeReader(file, os.Stdout)
        // r 读取内容后,会自动写入到 os.Stdout,所以控制台会显示文件中内容
        r.Read(make([]byte, 100))
    }
    

    io/ioutil 包常用函数

    虽然 io 包提供了不少类型、方法和函数,但有时候使用起来不是那么方便,比如读取一个文件中的所有内容。为此,Go 语言在io/ioutil包提供了一些常用、方便的I/O操作函数。

    接口类型转换 ioutil.NopCloser

    // 用一个无操作的 Close 方法包装 r,返回一个 io.ReadCloser 接口。Close 方法什么也不做,只是返回 nil
    func NopCloser(r io.Reader) io.ReadCloser
    

    ⭐️ 示例代码

    package main
    
    import (
        "fmt"
        "io/ioutil"
        "strings"
    )
    
    func main() {
        r := strings.NewReader("hello world")
        nc := ioutil.NopCloser(r)
        // Close 方法什么也不做,只是返回 nil
        nc.Close()
    
        p := make([]byte, 20)
        n, err := nc.Read(p)
        if err != nil {
            panic(err)
        }
        fmt.Printf("read %d bytes:%s", n, p) // read 11 bytes:hello world
    }
    

    读取全部字节 ioutil.ReadAll

    // 从 r 读取数据直到 EOF 或遇到 error,返回读取的数据和遇到的错误
    // 成功的调用返回的 err 为 nil 而非 EOF。因为本函数定义为读取 r 直到 EOF,它不会将读取返回的 EOF 视为应报告的错误
    func ReadAll(r io.Reader) ([]byte, error)
    

    ✏️ 示例代码

    package main
    
    import (
        "fmt"
        "io/ioutil"
        "os"
    )
    
    func main() {
        file, err := os.Open("./test.txt")
        if err != nil {
            panic(err)
        }
        defer file.Close()
    
        // os.File.Read(), io.ReadFull() 和 io.ReadAtLeast() 在读取之前都需要一个固定大小的 byte slice
        // 但 ioutil.ReadAll() 会读取 reader (这个例子中是file) 的每一个字节,然后把字节 slice 返回
        data, err := ioutil.ReadAll(file)
        if err != nil {
            panic(err)
        }
    
        fmt.Printf("Data as hex: %x\n", data)
        fmt.Printf("Data as string: %s\n", data)
        fmt.Println("Number of bytes read:", len(data))
    }
    

    读取目录 ioutil.ReadDir

    // 读取 dirname 目录,并返回排好序的文件和子目录名
    func ReadDir(dirname string) ([]os.FileInfo, error)
    

    示例代码

    package main
    
    import (
        "fmt"
        "io/ioutil"
    )
    
    func main() {
        // 输出当前目录下的所有文件(包括子目录)
        listAll(".", 0)
    }
    
    func listAll(path string, curHeight int) {
        fileInfos, err := ioutil.ReadDir(path)
        if err != nil {
            fmt.Println(err)
            return
        }
    
        for _, info := range fileInfos {
            if info.IsDir() {
                for tmpHeight := curHeight; tmpHeight > 0; tmpHeight-- {
                    fmt.Printf("|\t")
                }
                fmt.Println(info.Name(), "\\")
                listAll(path+"/"+info.Name(), curHeight+1)
            } else {
                for tmpHeight := curHeight; tmpHeight > 0; tmpHeight-- {
                    fmt.Printf("|\t")
                }
                fmt.Println(info.Name())
            }
        }
    }
    

    快读文件 ioutil.ReadFile

    io/ioutil包提供ReadFile函数可以处理打开文件、读取文件和关闭文件一系列的操作。如果需要简洁快速地读取文件到字节切片中,可以使用它。该方法定义如下:

    // 从 filename 指定的文件中读取数据并返回文件的内容
    // 成功的调用返回的 err 为 nil 而非 EOF。因为本函数定义为读取整个文件,它不会将读取返回的 EOF 视为应报告的错误
    func ReadFile(filename string) ([]byte, error)
    

    ✌ 示例代码

    package main
    
    import (
        "fmt"
        "io/ioutil"
    )
    
    func main() {
        // 读取文件到字节 slice 中
        data, err := ioutil.ReadFile("./test.txt")
        if err != nil {
            panic(err)
        }
    
        fmt.Printf("Data read: %s\n", data)
    }
    

    快写文件 ioutil.WriteFile

    ReadFile函数对应,io/ioutil包提供WriteFile函数可以处理创建或者打开文件、写入字节切片和关闭文件一系列的操作。如果需要简洁快速地写字节切片到文件中,可以使用它。该方法定义如下:

    // 向 filename 指定的文件中写入数据。如果文件不存在将按给出的权限创建文件,否则在写入数据之前清空文件
    func WriteFile(filename string, data []byte, perm os.FileMode) error
    

    ✍ 示例代码

    package main
    
    import (
        "io/ioutil"
    )
    
    func main() {
        // 向文件写入 "hello world!!!"
        err := ioutil.WriteFile("./test.txt", []byte("hello world!!!"), 0666)
        if err != nil {
            panic(err)
        }
    }
    

    创建临时目录 ioutil.TempDir

    // 在 dir 目录里创建一个新的、使用 prfix 作为前缀的临时文件夹,并返回文件夹的路径
    // 如果 dir 是空字符串,表明使用系统默认的临时目录(参见 os.TempDir 函数)中创建临时目录
    // 不同程序同时调用该函数会创建不同的临时目录,调用本函数的程序有责任在不需要临时文件夹时摧毁它
    func TempDir(dir, prefix string) (name string, err error)
    

    示例代码

    package main
    
    import (
        "fmt"
        "io/ioutil"
        "os"
    )
    
    func main() {
        // 创建临时目录
        name, err := ioutil.TempDir("F:", "temp")
        if err != nil {
            panic(err)
        }
        // 删除临时目录
        defer os.Remove(name)
        fmt.Println(name) // F:\temp2305919538
    }
    

    创建临时文件 ioutil.TempFile

    // 在 dir 目录下创建一个新的、使用 prefix 为前缀的临时文件,以读写模式打开该文件并返回 os.File 指针
    // 如果 dir 是空字符串,表明使用系统默认的临时目录(参见 os.TempDir 函数)中创建临时文件
    // 不同程序同时调用该函数会创建不同的临时文件,调用本函数的程序有责任在不需要临时文件时摧毁它
    func TempFile(dir, prefix string) (f *os.File, err error)
    

    示例代码

    package main
    
    import (
        "fmt"
        "io/ioutil"
        "os"
    )
    
    func main() {
        // 创建并打开临时文件
        f, err := ioutil.TempFile("F:", "temp")
        if err != nil {
            panic(err)
        }
        // 删除临时文件
        defer func() {
            f.Close()
            os.Remove(f.Name())
        }()
        fmt.Println(f.Name()) // F:\temp2379571275
    }
    

    参考

    1. io — 基本的 IO 接口
    2. ioutil — 方便的IO操作函数集
    3. Go语言的30个常用文件操作,总有一个你会用到
  • 相关阅读:
    004-DQN
    003-sarsa
    002-Q Leaning
    001-强化学习简介
    阿里云GPU服务器配置深度学习环境-远程访问-centos,cuda,cudnn,tensorflow,keras,jupyter notebook
    003-keras模型的保存于加载
    004-linux常用命令-网络命令
    004-linux常用命令-压缩解压命令
    004-linux常用命令-添加管理员用户
    004-linux常用命令-用户管理命令
  • 原文地址:https://www.cnblogs.com/zongmin/p/15684722.html
Copyright © 2020-2023  润新知