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个常用文件操作,总有一个你会用到
原文地址:https://www.cnblogs.com/zongmin/p/15684722.html