【Go学习】-02-2-标准库:log、errors、bytes、io

【Go学习】-02-2-标准库:log、errors、bytes、io

  • [1 log标准库](#1 log标准库)
    • [1.1 说明](#1.1 说明)
    • [1.2 Print](#1.2 Print)
    • [1.3 Panic](#1.3 Panic)
    • [1.4 Fatal](#1.4 Fatal)
    • [1.5 日志配置](#1.5 日志配置)
      • [1.5.1 前缀配置](#1.5.1 前缀配置)
      • [1.5.2 输出到文件](#1.5.2 输出到文件)
    • [1.6 自定义Logger](#1.6 自定义Logger)
  • [2 errors标准库](#2 errors标准库)
    • [2.1 使用](#2.1 使用)
    • [2.2 自定义错误](#2.2 自定义错误)
  • [3 bytes标准库](#3 bytes标准库)
    • [3.1 常用函数](#3.1 常用函数)
      • [3.1.1.1 转换](#3.1.1.1 转换)
      • [3.1.1.2 比较](#3.1.1.2 比较)
      • [3.1.1.3 清理](#3.1.1.3 清理)
      • [3.1.1.4 拆合](#3.1.1.4 拆合)
      • [3.1.1.5 字串](#3.1.1.5 字串)
      • [3.1.1.6 替换](#3.1.1.6 替换)
    • [3.2 Buffer类型](#3.2 Buffer类型)
      • [3.1.2.1 声明buffer](#3.1.2.1 声明buffer)
      • [3.1.2.2 往Buffer中写入数据](#3.1.2.2 往Buffer中写入数据)
      • [3.1.2.3 从Buffer中读取数据](#3.1.2.3 从Buffer中读取数据)
      • [3.1.2.4 其他方法](#3.1.2.4 其他方法)
    • [3.3 Reader类型](#3.3 Reader类型)
  • [4 io标准库](#4 io标准库)
    • [4.1 错误变量](#4.1 错误变量)
    • [4.2 基础接口](#4.2 基础接口)
      • [4.2.1 Reader接口](#4.2.1 Reader接口)
      • [4.2.2 Writer接口](#4.2.2 Writer接口)
      • [4.2.3 Seeker接口](#4.2.3 Seeker接口)
      • [4.2.4 Closer接口](#4.2.4 Closer接口)
    • [4.3 组合接口](#4.3 组合接口)
      • [4.3.1 ReadWriter接口](#4.3.1 ReadWriter接口)
      • [4.3.2 ReadCloser接口](#4.3.2 ReadCloser接口)
      • [4.3.3 WriteCloser接口](#4.3.3 WriteCloser接口)
      • [4.3.4 ReadWriteCloser接口](#4.3.4 ReadWriteCloser接口)
      • [4.3.5 ReadSeeker接口](#4.3.5 ReadSeeker接口)
      • [4.3.6 WriteSeeker接口](#4.3.6 WriteSeeker接口)
      • [4.3.7 ReadWriteSeeker接口](#4.3.7 ReadWriteSeeker接口)
    • [4.4 指定读写器读写接口](#4.4 指定读写器读写接口)
      • [4.4.1 ReaderFrom接口](#4.4.1 ReaderFrom接口)
      • [4.4.2 WriterTo接口](#4.4.2 WriterTo接口)
    • [4.5 指定偏移量读写接口](#4.5 指定偏移量读写接口)
      • [4.5.1 ReaderAt接口](#4.5.1 ReaderAt接口)
      • [4.5.2 WriterAt接口](#4.5.2 WriterAt接口)
    • [4.6 单个字节读写接口](#4.6 单个字节读写接口)
      • [4.6.1 ByteReader接口](#4.6.1 ByteReader接口)
      • [4.6.2 ByteScanner接口](#4.6.2 ByteScanner接口)
      • [4.6.3 ByteWriter接口](#4.6.3 ByteWriter接口)
      • [4.6.4 RuneReader接口](#4.6.4 RuneReader接口)
      • [4.6.5 RuneScanner接口](#4.6.5 RuneScanner接口)
      • [4.6.6 StringWriter接口](#4.6.6 StringWriter接口)
    • [4.7 结构体](#4.7 结构体)
      • [4.7.1 LimitedReader](#4.7.1 LimitedReader)
      • [4.7.2 PipeReader](#4.7.2 PipeReader)
      • [4.7.3 PipeWriter](#4.7.3 PipeWriter)
      • [4.7.4 SectionReader](#4.7.4 SectionReader)
    • [4.8 供外部调用的函数](#4.8 供外部调用的函数)
      • [4.8.1 Copy](#4.8.1 Copy)
      • [4.8.2 CopyBuffer](#4.8.2 CopyBuffer)
      • [4.8.3 CopyN](#4.8.3 CopyN)
      • [4.8.4 LimitReader](#4.8.4 LimitReader)
      • [4.8.5 MultiReader](#4.8.5 MultiReader)
      • [4.8.6 MultiWriter](#4.8.6 MultiWriter)
      • [4.8.7 Pipe](#4.8.7 Pipe)
      • [4.8.8 ReadAll](#4.8.8 ReadAll)
      • [4.8.9 ReadAtLeast](#4.8.9 ReadAtLeast)
      • [4.8.10 ReadFull](#4.8.10 ReadFull)
      • [4.8.11 SectionReader](#4.8.11 SectionReader)
        • [4.8.11.1 NewSectionReader](#4.8.11.1 NewSectionReader)
        • [4.8.11.2 SectionReader.Read](#4.8.11.2 SectionReader.Read)
        • [4.8.11.3 SectionReader.ReadAt](#4.8.11.3 SectionReader.ReadAt)
        • [4.8.11.4 SectionReader.Seek](#4.8.11.4 SectionReader.Seek)
        • [4.8.11.5 SectionReader.Size](#4.8.11.5 SectionReader.Size)
      • [4.8.12 TeeReader](#4.8.12 TeeReader)
      • [4.8.13 WriteString](#4.8.13 WriteString)

1 log标准库

golang内置了log包,实现简单的日志服务。通过调用log包的函数,可以实现简单的日志打印功能。

log包定义了Logger类型,该类型提供了一些格式化输出的方法。

log包也提供了一个预定义的"标准"logger,可以通过调用函数Print系列(Print|Printf|Println)、Fatal系列(Fatal|Fatalf|Fatalln)、和Panic系列(Panic|Panicf|Panicln)来使用。

1.1 说明

log包中有3个系列的日志打印函数,分别print系列panic系列fatal系列.log是带时间的。

函数系列 说明 作用
Print Print/Printf/Println 单纯打印日志
Panic Panic/Panicf/Panicln 打印日志,抛出panic异常
Fatal Fatal/Fatalf/Fatalln 打印日志,强制结束程序(os.Exit(1)),defer函数不会执行

log包主要提供了3类接口,分别是print系列、panic系列、fatal系列,对每一类接口其提供了3种调用方式。

1.2 Print

单纯打印日志

go 复制代码
package main

import "log"

func main() {
	log.Print("this is a log")
    log.Printf("this is a log: %d", 100) // 格式化输出
	name := "zhangsan"
	age := 20
	log.Println(name, " ", age)
}
2025/01/07 16:18:55 this is a log
2025/01/07 16:18:55 this is a log: 100
2025/01/07 16:18:55 zhangsan   20

1.3 Panic

打印出日志并且抛出panic异常,在panic之后声明的代码将不会执行。

go 复制代码
package main

import (
	"fmt"
	"log"
)

func main() {
	defer fmt.Println("发生了 panic错误!")
	log.Print("this is a log")
	log.Panic("this is a panic log ")
	fmt.Println("运行结束。。。")
}
2025/01/07 16:21:05 this is a log
2025/01/07 16:21:05 this is a panic log
发生了 panic错误!
panic: this is a panic log

goroutine 1 [running]:
log.Panic({0xc00002ded0?, 0x0?, 0x2?})
        D:/software_code/Go/1.23.4/src/log/log.go:432 +0x5a
main.main()
        F:/Code/Golang/TuLing/workPath/standard/log.go:18 +0xef

1.4 Fatal

将日志内容打印输出,接着调用系统的os.Exit(1)接口,强制退出程序并返回状态1,但是有一点需要注意的是,由于直接调用系统os接口退出,defer函数不会调用。

go 复制代码
package main

import (
	"fmt"
	"log"
)

func main() {
	defer fmt.Println("defer。。。")
	log.Print("this is a log")
	log.Fatal("this is a fatal log")
	fmt.Println("运行结束。。。")
}
go 复制代码
2025/01/07 16:22:10 this is a log
2025/01/07 16:22:10 this is a fatal log

1.5 日志配置

默认情况下log只会打印出时间,但是实际情况下我们还需要获取文件名,行号等信息,log包提供给我们定制的接口。

方法 说明
func Flags() int 返回标准log输出配置
func SetFlags(flag int) 设置标准log输出配置
go 复制代码
const (
    // 控制输出日志信息的细节,不能控制输出的顺序和格式。
    // 输出的日志在每一项后会有一个冒号分隔,例如2022/07/23 01:23:23.123123 /a/b/c/d.go:23: message
    Ldate         = 1 << iota     // 日期,2022/07/23
    Ltime                         // 时间,01:23:23
    Lmicroseconds                 // 微秒级别的时间,01:23:23.123123(用于增强Ltime位)
    Llongfile                     // 文件全路径名+行号,/a/b/c/d.go:23
    Lshortfile                    // 文件名+行号,d.go:23(会覆盖掉Llongfile)
    LUTC                          // 使用UTC时间
    LstdFlags     = Ldate | Ltime // 标准logger的初始值
)
go 复制代码
package main

import (
	"fmt"
	"log"
)

func main() {
	i := log.Flags()
	fmt.Printf("i: %v\n", i)
	log.SetFlags(log.Ldate | log.Ltime | log.Llongfile)
	log.Print("this is a log")
}
i: 3
2025/01/07 16:23:57 F:/Code/Golang/TuLing/workPath/standard/log.go:33: this is a log

1.5.1 前缀配置

方法 说明
func Prefix() string 返回日志的前缀配置
func SetPrefix(prefix string) 设置日志前缀
go 复制代码
package main

import (
	"fmt"
	"log"
)

func main() {
	s := log.Prefix()
	fmt.Printf("s: %v\n", s)
	log.SetPrefix("[MyLog] ")
	s = log.Prefix()
	fmt.Printf("s: %v\n", s)
	log.Print("this is a log...")
}
s:
s: [MyLog]
[MyLog] 2025/01/07 16:27:26 this is a log...

1.5.2 输出到文件

log包提供了func SetOutput(w io.Writer)函数,将日志输出到文件中。

go 复制代码
package main

import (
	"log"
	"os"
)

func main() {
	f, err := os.OpenFile("test.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		log.Panic("打开日志文件异常")
	}
	log.SetOutput(f)
	log.Print("this is a file log...")
}

1.6 自定义Logger

log包中提供了func New(out io.Writer, prefix string, flag int) *Logger函数来实现自定义logger。

go 复制代码
package main

import (
	"log"
	"os"
)

var logger *log.Logger

func init() {
	logFile, err := os.OpenFile("test.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		log.Panic("打开日志文件异常")
	}
	logger = log.New(logFile, "[Mylog]", log.Ldate|log.Ltime|log.Lshortfile)
}

func main() {
	logger.Println("自定义logger")
}

2 errors标准库

errors包实现了操作错误的函数。go语言使用error类型来返回函数执行过程中遇到的错误,如果返回的error值为nil,则表示未遇到错误,否则error会返回一个字符串,用于说明遇到了什么错误。

go 复制代码
type error interface {
    Error() string
}

error不一定表示一个错误,它可以表示任何信息,比如io包中就用error类型的io.EOF表示数据读取结束,而不是遇到了什么错误。

2.1 使用

go 复制代码
func New(text string) error
go 复制代码
package main

import (
	"errors"
	"fmt"
)

func check(s string) (string, error) {
	if s == "" {
		err := errors.New("字符串不能为空")
		return "", err
	} else {
		return s, nil
	}
}

func main() {
	s, err := check("")
	if err != nil {
		fmt.Printf("err: %v\n", err.Error())
	} else {
		fmt.Printf("s: %v\n", s)
	}
}

2.2 自定义错误

go允许函数具有多返回值,但通常你不会想写太多的返回值在函数定义上,而标准库内置的error String类型由于只能表达字符串错误信息显然受限。

所以,可以通过实现error接口的方式,来扩展错误返回。

go 复制代码
package main

import (
	"fmt"
	"time"
)

// 自定义error类型
type MyError struct {
	When time.Time //发生错误的时间
	What string    //错误文本信息
}

func (e MyError) Error() string {
	return fmt.Sprintf("%v: %v", e.When, e.What)
}

func oops() error {
	return MyError{
		time.Date(1989, 3, 15, 22, 30, 0, 0, time.UTC),
		"the file system has gone away",
	}
}

func main() {
	if err := oops(); err != nil {
		fmt.Println(err)
	}
}

3 bytes标准库

bytes包提供了对字节切片进行读写操作的一系列函数,字节切片处理的函数比较多分为基本处理函数、比较函数、后缀检查函数、索引函数、分割函数、大小写处理函数和子切片处理函数等。

3.1 常用函数

3.1.1.1 转换

函数 说明
func ToUpper(s []byte) []byte 将 s 中的所有字符修改为大写格式返回。
func ToLower(s []byte) []byte 将 s 中的所有字符修改为小写格式返回
func ToTitle(s []byte) []byte 将 s 中的所有字符修改为标题格式返回
func ToUpperSpecial(_case unicode.SpecialCase, s []byte) []byte 使用指定的映射表将 s 中的所有字符修改为大写格式返回
func ToLowerSpecial(_case unicode.SpecialCase, s []byte) []byte 使用指定的映射表将 s 中的所有字符修改为小写格式返回
func ToTitleSpecial(_case unicode.SpecialCase, s []byte) []byte 使用指定的映射表将 s 中的所有字符修改为标题格式返回
func Title(s []byte) []byte 将 s 中的所有单词的首字符修改为 Title 格式返回。(缺点:不能很好的处理以 Unicode 标点符号分隔的单词。)
go 复制代码
package main

import (
	"bytes"
	"fmt"
)

func main() {
	var b = []byte("seafood") //强制类型转换

	a := bytes.ToUpper(b)
	fmt.Println(a, b) 

	c := b[0:4]
	c[0] = 'A'
	fmt.Println(c, b)
}
[83 69 65 70 79 79 68] [115 101 97 102 111 111 100]
[65 101 97 102] [65 101 97 102 111 111 100]

3.1.1.2 比较

函数 说明
func Compare(a, b []byte) int 比较两个 []byte,nil 参数相当于空 []byte。a < b 返回 -1;a == b 返回 0;a > b 返回 1
func Equal(a, b []byte) bool 判断 a、b 是否相等,nil 参数相当于空 []byte
func EqualFold(s, t []byte) bool 判断 s、t 是否相似,忽略大写、小写、标题三种格式的区别
go 复制代码
package main

import (
	"bytes"
	"fmt"
)

func main() {
	s1 := "Φφϕ kKK"
	s2 := "ϕΦφ KkK"

	// 看看 s1 里面是什么
	for _, c := range s1 {
		fmt.Printf("%-5x", c)
	}
	fmt.Println()
	// 看看 s2 里面是什么
	for _, c := range s2 {
		fmt.Printf("%-5x", c)
	}
	fmt.Println()
	// 看看 s1 和 s2 是否相似
	fmt.Println(bytes.EqualFold([]byte(s1), []byte(s2)))
}
3a6  3c6  3d5  20   6b   4b   212a
3d5  3a6  3c6  20   212a 6b   4b
true

3.1.1.3 清理

  • func Trim(s []byte, cutset string) []byte

    去掉 s 两边包含在 cutset 中的字符(返回 s 的切片)

  • func TrimLeft(s []byte, cutset string) []byte

    去掉 s 左边包含在 cutset 中的字符(返回 s 的切片)

  • func TrimRight(s []byte, cutset string) []byte

    去掉 s 右边包含在 cutset 中的字符(返回 s 的切片)

  • func TrimFunc(s []byte, f func(r rune) bool) []byte

    去掉 s 两边符合 f函数====返回值是true还是false 要求的字符(返回 s 的切片)

  • func TrimLeftFunc(s []byte, f func(r rune) bool) []byte

    去掉 s左边符合 f函数====返回值是true还是false 要求的字符(返回 s 的切片)

  • func TrimRightFunc(s []byte, f func(r rune) bool) []byte

    去掉 s右边符合 f函数====返回值是true还是false 要求的字符(返回 s 的切片)

  • func TrimSpace(s []byte) []byte

    去掉 s 两边的空白(unicode.IsSpace)(返回 s 的切片)

  • func TrimPrefix(s, prefix []byte) []byte

    去掉 s 的前缀 prefix(返回 s 的切片)

  • func TrimSuffix(s, suffix []byte) []byte

    去掉 s 的后缀 suffix(返回 s 的切片)

go 复制代码
package main

import (
	"bytes"
	"fmt"
)

func main() {
	bs := [][]byte{ //[][]byte 字节切片 二维数组
		[]byte("Hello World !"),
		[]byte("Hello 世界!"),
		[]byte("hello golang ."),
	}

	f := func(r rune) bool {
		return bytes.ContainsRune([]byte("!!. "), r) //判断r字符是否包含在"!!. "内
	}

	for _, b := range bs { //range bs  取得下标和[]byte
		fmt.Printf("去掉两边: %q\n", bytes.TrimFunc(b, f)) //去掉两边满足函数的字符
	}

	for _, b := range bs {
		fmt.Printf("去掉前缀: %q\n", bytes.TrimPrefix(b, []byte("Hello "))) //去掉前缀
	}
}
去掉两边: "Hello World"
去掉两边: "Hello 世界"
去掉两边: "hello golang"
去掉前缀: "World !"
去掉前缀: "世界!"
去掉前缀: "hello golang ."

3.1.1.4 拆合

  • func Split(s, sep []byte) [][]byte

    Split 以 sep 为分隔符将 s 切分成多个子串,结果不包含分隔符。如果 sep 为空,则将 s 切分成 Unicode 字符列表。

  • func SplitN(s, sep []byte, n int) [][]byte

    SplitN 可以指定切分次数 n,超出 n 的部分将不进行切分。

  • func SplitAfter(s, sep []byte) [][]byte

    功能同 Split,只不过结果包含分隔符(在各个子串尾部)。

  • func SplitAfterN(s, sep []byte, n int) [][]byte

    功能同 SplitN,只不过结果包含分隔符(在各个子串尾部)。

  • func Fields(s []byte) [][]byte

    以连续空白为分隔符将 s 切分成多个子串,结果不包含分隔符。

  • func FieldsFunc(s []byte, f func(rune) bool) [][]byte

    以符合 f 的字符为分隔符将 s 切分成多个子串,结果不包含分隔符。

  • func Join(s [][]byte, sep []byte) []byte

    以 sep 为连接符,将子串列表 s 连接成一个字节串。

  • func Repeat(b []byte, count int) []byte

    将子串 b 重复 count 次后返回。

go 复制代码
package main

import (
	"bytes"
	"fmt"
)

func main() {
	b := []byte("  Hello   World !  ")
	fmt.Printf("b: %q\n", b)
	fmt.Printf("%q\n", bytes.Split(b, []byte{' '}))

	fmt.Printf("%q\n", bytes.Fields(b))

	f := func(r rune) bool {
		return bytes.ContainsRune([]byte(" !"), r)
	}
	fmt.Printf("%q\n", bytes.FieldsFunc(b, f))
}
b: "  Hello   World !  "
["" "" "Hello" "" "" "World" "!" "" ""]
["Hello" "World" "!"]
["Hello" "World"]

3.1.1.5 字串

  • func HasPrefix(s, prefix []byte) bool

    判断 s 是否有前缀 prefix

  • func HasSuffix(s, suffix []byte) bool

    判断 s 是否有后缀 suffix

  • func Contains(b, subslice []byte) bool

    判断 b 中是否包含子串 subslice

  • func ContainsRune(b []byte, r rune) bool

    判断 b 中是否包含子串 字符 r

  • func ContainsAny(b []byte, chars string) bool

    判断 b 中是否包含 chars 中的任何一个字符

  • func Index(s, sep []byte) int

    查找子串 sep在 s 中第一次出现的位置,找不到则返回 -1

  • func IndexByte(s []byte, c byte) int

    查找子串 字节 c在 s 中第一次出现的位置,找不到则返回 -1

  • func IndexRune(s []byte, r rune) int

    查找子串字符 r在 s 中第一次出现的位置,找不到则返回 -1

  • func IndexAny(s []byte, chars string) int

    查找 chars 中的任何一个字符在 s 中第一次出现的位置,找不到则返回 -1。

  • func IndexFunc(s []byte, f func(r rune) bool) int

    查找符合 f 的字符在 s 中第一次出现的位置,找不到则返回 -1。

  • func LastIndex(s, sep []byte) int

    功能同上,只不过查找最后一次出现的位置。

  • func LastIndexByte(s []byte, c byte) int

    功能同上,只不过查找最后一次出现的位置。

  • func LastIndexAny(s []byte, chars string) int

    功能同上,只不过查找最后一次出现的位置。

  • func LastIndexFunc(s []byte, f func(r rune) bool) int

    功能同上,只不过查找最后一次出现的位置。

  • func Count(s, sep []byte) int

    获取 sep 在 s 中出现的次数(sep 不能重叠)。

go 复制代码
package main

import (
	"bytes"
	"fmt"
)

func main() {
	b := []byte("hello golang") //字符串强转为byte切片
	sublice1 := []byte("hello")
	sublice2 := []byte("Hello")
	fmt.Println(bytes.Contains(b, sublice1)) //true
	fmt.Println(bytes.Contains(b, sublice2)) //false

	s := []byte("hellooooooooo")
	sep1 := []byte("h")
	sep2 := []byte("l")
	sep3 := []byte("o")
	fmt.Println(bytes.Count(s, sep1)) //1
	fmt.Println(bytes.Count(s, sep2)) //2
	fmt.Println(bytes.Count(s, sep3)) //9
}

3.1.1.6 替换

  • func Replace(s, old, new []byte, n int) []byte

    将 s 中前 n 个 old 替换为 new,n < 0 则替换全部。

  • func Map(mapping func(r rune) rune, s []byte) []byte

    将 s 中的字符替换为 mapping® 的返回值,如果 mapping 返回负值,则丢弃该字符。

  • func Runes(s []byte) []rune

    将 s 转换为 []rune 类型返回

go 复制代码
package main

import (
	"bytes"
	"fmt"
)

func main() {
	s := []byte("hello,world")
	old := []byte("o")
	news := []byte("ee")
	fmt.Println(string(bytes.Replace(s, old, news, 0)))  //hello,world
	fmt.Println(string(bytes.Replace(s, old, news, 1)))  //hellee,world
	fmt.Println(string(bytes.Replace(s, old, news, 2)))  //hellee,weerld
	fmt.Println(string(bytes.Replace(s, old, news, -1))) //hellee,weerld

	s1 := []byte("你好世界")
	r := bytes.Runes(s1)
	fmt.Println("转换前字符串的长度:", len(s1)) //12
	fmt.Println("转换后字符串的长度:", len(r))  //4
}

3.2 Buffer类型

缓冲区是具有读取和写入方法的可变大小的字节缓冲区

Buffer的零值是准备使用的空缓冲区

go 复制代码
type Buffer struct {
	buf      []byte // contents are the bytes buf[off : len(buf)]
	off      int    // read at &buf[off], write at &buf[len(buf)]
	lastRead readOp // last read operation, so that Unread* can work correctly.
}

3.1.2.1 声明buffer

  • var b bytes.Buffer

    直接定义一个Buffer变量,不用初始化,可以直接使用

  • b := new(bytes.Buffer)

    使用New返回Buffer变量

  • b := bytes.NewBuffer(s []byte)

    从一个[]byte切片,构造一个Buffer

  • b := bytes.NewBufferString(s string)

    从一个string变量,构造一个Buffer

3.1.2.2 往Buffer中写入数据

  • b.Write(d []byte)

    将切片d写入Buffer数据

  • b.WriteString(s string)

    将字符串s写入Buffer尾部

  • b.WriteByte(c byte)

    将字符c写入Buffer尾部

  • b.WriteRune(r rune)

    将一个rune类型的数据放到缓冲器的尾部

  • b.WriteTo(w io.Writer)

    将Buffer中的内容输出到实现了io.Writer接口的可写入对象中

3.1.2.3 从Buffer中读取数据

  • b.Read(c)

    一次读取8个byte到c容器中,每次读取新的8个byte覆盖c中原来的内容

    b 读取 8 字节数据到 c 中。此时 b 内部的数据会被消费,b 的内容会被改变。

  • b.ReadByte()

    读取第一个byte,b的第一个byte被拿掉,赋值给 a => a, _ := b.ReadByte()

  • b.ReadRune()

    读取第一个rune,b的第一个rune被拿掉,赋值给 r => r, _ := b.ReadRune()

  • b.ReadBytes(delimiter byte)

    需要一个byte作为分隔符,读的时候从缓冲器里找第一个出现的分隔符(delim),找到后,把从缓冲器头部开始到分隔符之间的所有byte进行返回,作为byte类型的slice,返回后,缓冲器也会空掉一部分

  • b.ReadString(delimiter byte)

    需要一个byte作为分隔符,读的时候从缓冲器里找第一个出现的分隔符(delim),找到后,把从缓冲器头部开始到分隔符之间的所有byte进行返回,作为字符串返回,返回后,缓冲器也会空掉一部分

  • b.ReadFrom(i io.Reader)

    从一个实现io.Reader接口的r,把r里的内容读到缓冲器里,n返回读的数量

go 复制代码
package main

import (
	"bytes"
	"fmt"
)

func main() {
	rd := bytes.NewBufferString("Hello World!")
	buf := make([]byte, 6)
	// 获取数据切片
	b := rd.Bytes()
	// 读出一部分数据,看看切片有没有变化
	rd.Read(buf)
	fmt.Printf("%s\n", rd.String())
	fmt.Printf("%s\n\n", b)

	// 写入一部分数据,看看切片有没有变化
	rd.Write([]byte("abcdefg"))
	fmt.Printf("%s\n", rd.String())
	fmt.Printf("%s\n\n", b)

	// 再读出一部分数据,看看切片有没有变化
	rd.Read(buf)
	fmt.Printf("%s\n", rd.String())
	fmt.Printf("%s\n", b)
}
Hello World!
World!
Hello World!

World!abcdefg
Hello World!

abcdefg
Hello World!

rd.Bytes() 返回的切片引用是一个快照

  • b := rd.Bytes() 会返回 rd 内部字节切片的一个引用。
  • rd.Bytes() 返回的是一个切片的 "快照" ,即它返回的是对 rd 内部字节数组的一个引用,但 并不是一个动态更新的视图 。当你调用 rd.Read(buf) 后,rd 内部的字节数组的内容发生变化,但是 b 仍然指向最初的内存地址。

3.1.2.4 其他方法

  • func (b *Buffer) Len() int

    未读取部分的数据长度

  • func (b *Buffer) Cap() int

    获取缓存的容量

  • func (b *Buffer) Next(n int) []byte

    读取前 n 字节的数据并以切片形式返回,如果数据长度小于 n,则全部读取。切片只在下一次读写操作前合法。

  • func (b *Buffer) Bytes() []byte

    引用未读取部分的数据切片(不移动读取位置)

  • func (b *Buffer) String() string

    返回未读取部分的数据字符串(不移动读取位置)

  • func (b *Buffer) Grow(n int)

    自动增加缓存容量,以保证有 n 字节的剩余空间。如果 n 小于 0 或无法增加容量则会 panic。

  • func (b *Buffer) Truncate(n int)

    将数据长度截短到 n 字节,如果 n 小于 0 或大于 Cap 则 panic。

  • func (b *Buffer) Reset()

    重设缓冲区,清空所有数据(包括初始内容)。

3.3 Reader类型

go 复制代码
type Reader struct {
	s        []byte
	i        int64 // current reading index
	prevRune int   // index of previous rune; or < 0
}

Reader实现了io.Reader, io.ReaderAt, io.WriterTo, io.Seeker, io.ByteScanner, io.RuneScanner接口

  • func NewReader(b []byte) *Reader

    将 b 包装成 bytes.Reader 对象。

  • func (r *Reader) Len() int

    返回未读取部分的数据长度

  • func (r *Reader) Size() int64

    返回底层数据的总长度,方便 ReadAt 使用,返回值永远不变。

  • func (r *Reader) Reset(b []byte)

    将底层数据切换为 b,同时复位所有标记(读取位置等信息)。

go 复制代码
package main

import (
	"bytes"
	"fmt"
)

func main() {
	data := "123456789"
	//通过[]byte创建Reader
	re := bytes.NewReader([]byte(data))
	//返回未读取部分的长度
	fmt.Println("re len : ", re.Len())
	//返回底层数据总长度
	fmt.Println("re size : ", re.Size())

	fmt.Println("---------------")

	buf := make([]byte, 2)
	for {
		//读取数据
		n, err := re.Read(buf)
		if err != nil {
			break
		}
		fmt.Println(string(buf[:n]))
	}

}
go 复制代码
package main

import (
	"bytes"
	"fmt"
)

func main() {
	data := "123456789"
	//通过[]byte创建Reader
	re := bytes.NewReader([]byte(data))

	buf := make([]byte, 2)

	re.Seek(0, 0)
	//设置偏移量
	for {
		//一个字节一个字节的读
		b, err := re.ReadByte()
		if err != nil {
			break
		}
		fmt.Println(string(b))
	}
	fmt.Println("----------------")

	re.Seek(0, 0)
	off := int64(0)
	for {
		//指定偏移量读取
		n, err := re.ReadAt(buf, off)
		if err != nil {
			break
		}
		off += int64(n)
		fmt.Println(off, string(buf[:n]))
	}

}

4 io标准库

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

4.1 错误变量

  • var EOF = errors.New("EOF")

    正常输入结束Read返回EOF,如果在一个结构化数据流中EOF在不期望的位置出现了,则应返回错误ErrUnexpectedEOF或者其它给出更多细节的错误。

  • var ErrClosedPipe = errors.New("io: read/write on closed pipe")

    当从一个已关闭的Pipe读取或者写入时,会返回ErrClosedPipe。

  • var ErrNoProgress = errors.New("multiple Read calls return no data or error")

    某些使用io.Reader接口的客户端如果多次调用Read都不返回数据也不返回错误时,就会返回本错误,一般来说是io.Reader的实现有问题的标志。

  • var ErrShortBuffer = errors.New("short buffer")

    ErrShortBuffer表示读取操作需要大缓冲,但提供的缓冲不够大。

  • var ErrShortWrite = errors.New("short write")

    ErrShortWrite表示写入操作写入的数据比提供的少,却没有显式的返回错误。

  • var ErrUnexpectedEOF = errors.New("unexpected EOF")

    ErrUnexpectedEOF表示在读取一个固定尺寸的块或者数据结构时,在读取未完全时遇到了EOF。

4.2 基础接口

4.2.1 Reader接口

go 复制代码
type Reader interface {
	Read(p []byte) (n int, err error)	
}

Read 将 len个字节读取到 p 中。它返回读取的字节数 n(0 <= n <= len)以及任何遇到的错误。即使 Read 返回的 n < len,它也会在调用过程中使用 p的全部作为暂存空间。若一些数据可用但不到 len 个字节,Read 会照例返回可用的东西,而不是等待更多。

当 Read 在成功读取 n > 0 个字节后遇到一个错误或 EOF 情况,它就会返回读取的字节数。它会从相同的调用中返回(非nil的)错误或从随后的调用中返回错误(和 n == 0)。这种一般情况的一个例子就是 Reader 在输入流结束时会返回一个非零的字节数,可能的返回不是 err == EOF 就是 err == nil。无论如何,下一个 Read 都应当返回 0, EOF。

调用者应当总在考虑到错误 err 前处理 n > 0 的字节。这样做可以在读取一些字节,以及允许的 EOF 行为后正确地处理I/O错误。

Read 的实现会阻止返回零字节的计数和一个 nil 错误,调用者应将这种情况视作空操作。

实例演示:

go 复制代码
package main

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

func main() {
	f, err := os.Open("a.txt")
	if err != nil {
		fmt.Printf("err: %v\n", err)
		return
	}
	defer f.Close()

	buf := make([]byte, 12) // 实例化一个长度为4的[]byte

	for {
		n, err2 := f.Read(buf) // 将内容读至buf
		if n == 0 || err2 == io.EOF {
			fmt.Println("文件以读取完毕")
			break
		}
		fmt.Println(string(buf[:n]))
	}

}

其中a.txt内容为:

txt 复制代码
 hello world

4.2.2 Writer接口

go 复制代码
type Writerinterface {
	Write(p []byte) (n int, err error)	
}

Write 将 len个字节从 p 中写入到基本数据流中。它返回从 p 中被写入的字节数n(0 <= n <= len)以及任何遇到的引起写入提前停止的错误。若 Write 返回的n < len,它就必须返回一个非nil的错误。Write 不能修改此切片的数据,即便它是临时的。

实例演示:

go 复制代码
package main

import (
	"os"
)

func main() {
	f, _ := os.OpenFile("a.txt", os.O_RDWR|os.O_APPEND, 0775) // 以读写模式打开文件,并且在写操作时将数据附加到文件尾部
	f.Write([]byte(" hello golang"))
	f.Close()
}

a.txt内容更新为:

txt 复制代码
 hello world hello golang

其中a.txt初始内容为:

txt 复制代码
 hello world

4.2.3 Seeker接口

go 复制代码
type Seeker interface {
    Seek(offset int64, whence int) (int64, error)
}

Seeker 用来移动数据的读写指针

Seek 设置下一次读写操作的指针位置,每次的读写操作都是从指针位置开始的

  • whence 的含义:

    • 如果 whence 为 0:表示从数据的开头开始移动指针
    • 如果 whence 为 1:表示从数据的当前指针位置开始移动指针
    • 如果 whence 为 2:表示从数据的尾部开始移动指针
  • offset 是指针移动的偏移量

    返回移动后的指针位置和移动过程中遇到的任何错误

实例演示:

go 复制代码
package main

import (
	"fmt"
	"os"
)

func main() {
	f, _ := os.Open("a.txt") // 打开文件后,光标默认在文件开头
	f.Seek(3, 0)             // 从索引值为3处开始读
	buf := make([]byte, 10)  // 设置缓冲区
	n, _ := f.Read(buf)      // 将内容读到缓冲区内
	fmt.Printf("n: %v\n", n)
	fmt.Printf("string(buf): %v\n", string(buf))
	f.Close()
}

其中a.txt内容为:

txt 复制代码
 hello world

4.2.4 Closer接口

go 复制代码
type Closer interface {
	Close() error
}

Closer关闭的接口, 带有Close() 方法, 但是行为没有定义,所以 可以特定行为来实现

在整个标准库内都没有对Closer的引用,只有实现,用法都是开启某某连接/流,在用完/报错后在进行Close的操作。

4.3 组合接口

组合接口是对多个接口进行了组合,当同时实现多个接口时,可以使用组合接口进行传递

4.3.1 ReadWriter接口

go 复制代码
type ReadWriter interface {
	Reader
	Writer
}

ReadWriter接口聚合了基本的读写操作。

4.3.2 ReadCloser接口

go 复制代码
type ReadCloser interface {
	Reader
	Closer
}

ReadCloser就是Reader+Closer,例如在ioutil中的NopCloser方法返回的就是一个ReadCloser,但是里面的Close就是个空函数,毫无作用。

4.3.3 WriteCloser接口

go 复制代码
type WriteCloser interface {
	Writer
	Closer
}

WriteCloser接口聚合了基本的写入和关闭操作。

4.3.4 ReadWriteCloser接口

go 复制代码
type ReadWriteCloser interface {
	Reader
	Writer
	Closer
}

ReadWriteCloser接口聚合了基本的读写和关闭操作。

4.3.5 ReadSeeker接口

go 复制代码
type ReadSeeker interface {
	Reader
	Seeker
}

ReadSeeker接口聚合了基本的读取和移位操作。

4.3.6 WriteSeeker接口

go 复制代码
type WriteSeeker interface {
	Writer
	Seeker
}

WriteSeeker接口聚合了基本的写入和移位操作。

4.3.7 ReadWriteSeeker接口

go 复制代码
type ReadWriteSeeker interface {
	Reader
	Writer
	Seeker
}

ReadWriteSeeker接口聚合了基本的读写和移位操作

4.4 指定读写器读写接口

4.4.1 ReaderFrom接口

go 复制代码
type ReaderFrom interface {
	ReadFrom(r Reader) (n int64, err error)		
}

ReadFrom 从 r 中读取数据到对象的数据流中

直到 r 返回 EOF 或 r 出现读取错误为止

返回值 n 是读取的字节数

返回值 err 就是 r 的返回值 err

4.4.2 WriterTo接口

go 复制代码
type WriterTo interface {
	WriteTo(w Writer) (n int64, err error)		
}

WriterTo 将对象的数据流写入到 w 中

直到对象的数据流全部写入完毕或遇到写入错误为止

返回值 n 是写入的字节数

返回值 err 就是 w 的返回值 err

4.5 指定偏移量读写接口

4.5.1 ReaderAt接口

go 复制代码
type ReaderAt interface {
    ReadAt(p []byte, off int64) (n int, err error)
}

ReadAt 从对象数据流的 off 处读出数据到 p 中

  • 忽略数据的读写指针,从数据的起始位置偏移 off 处开始读取
  • 如果对象的数据流只有部分可用,不足以填满 p则 ReadAt 将等待所有数据可用之后,继续向 p 中写入直到将 p 填满后再返回,在这点上 ReadAt 要比 Read 更严格
  • 返回读取的字节数 n 和读取时遇到的错误
  • 如果 n < len,则需要返回一个 err 值来说明为什么没有将 p 填满(比如 EOF)
  • 如果 n > len,而且对象的数据没有全部读完,则err 将返回 nil
  • 如果 n = len,而且对象的数据刚好全部读完,则err 将返回 EOF 或者 nil(不确定)

4.5.2 WriterAt接口

go 复制代码
type WriterAt interface {
    WriteAt(p []byte, off int64) (n int, err error)
}

WriteAt 将 p 中的数据写入到对象数据流的 off 处

  • 忽略数据的读写指针,从数据的起始位置偏移 off 处开始写入
  • 返回写入的字节数和写入时遇到的错误
  • 如果 n < len,则必须返回一个 err 值来说明为什么没有将 p 完全写入

4.6 单个字节读写接口

4.6.1 ByteReader接口

go 复制代码
type ByteReader interface {
	ReadByte() (byte, error)
}

ByteReader是基本的ReadByte方法的包装。

ReadByte读取输入中的单个字节并返回。如果没有字节可读取,会返回错误。

4.6.2 ByteScanner接口

go 复制代码
type ByteScanner interface {
    ByteReader
    UnreadByte() error
}

ByteScanner接口在基本的ReadByte方法之外还添加了UnreadByte方法。

UnreadByte方法让下一次调用ReadByte时返回之前调用ReadByte时返回的同一个字节。连续调用两次UnreadByte方法而中间没有调用ReadByte时,可能会导致错误。

4.6.3 ByteWriter接口

go 复制代码
type ByteWriter interface {
	WriteByte(c byte) error
}

包装 WriteByte 单个字节写入方法的接口

4.6.4 RuneReader接口

go 复制代码
type RuneReader interface {
	ReadRune() (r rune, size int, err error)	
}

ReadRune 方法的包装,读取单个UTF-8编码的Unicode字符,并返回rune及其字节大小。如果没有可用字符,将设置err

4.6.5 RuneScanner接口

go 复制代码
type RuneScanner interface {
	RuneReader
	UnreadRune() error
}

RuneScanner接口在基本的ReadRune方法之外还添加了UnreadRune方法。

UnreadRune方法让下一次调用ReadRune时返回之前调用ReadRune时返回的同一个utf-8字符。连续调用两次UnreadRune方法而中间没有调用ReadRune时,可能会导致错误。

4.6.6 StringWriter接口

go 复制代码
type StringWriter interface {
	WriteString(s string) (n int, err error)
}

字符串写入方法WriteString的包装

4.7 结构体

4.7.1 LimitedReader

go 复制代码
type LimitedReader struct {
    R   Reader // underlying reader
    N   int64  // max bytes remaining
}

LimitedReader从R读取,但将返回的数据量限制为N个字节。每次读取更新N以标记剩余可以读取的字节数。Read在N<=0时或基础R返回EOF时返回EOF。

具体实现方法为:func LimitReader(r Reader, n int64) Reader

4.7.2 PipeReader

go 复制代码
type PipeReader struct {
    // 内含隐藏或非导出字段
}

PipeReader是一个管道的读取端。

具体实现方法有:

  1. func (r *PipeReader) Read(data []byte) (n int, err error)
    Read实现了标准的读取接口:它从管道中读取数据,阻塞直到写入端到达或写入端被关闭。如果用错误关闭写入端,则返回错误为ERR;否则ERR为EOF。
  2. func (r *PipeReader) Close() error
    Close关闭读取器;关闭后如果对管道的写入端进行写入操作,就会返回(0, ErrClosedPip)。
  3. func (r *PipeReader) CloseWithError(err error) error
    CloseWithError类似Close方法,但将调用Write时返回的错误改为err。

4.7.3 PipeWriter

go 复制代码
type PipeWriter struct {
    // 内含隐藏或非导出字段
}

PipeWriter是一个管道的写入端。

具体实现方法有:

  1. func (w *PipeWriter) Write(data []byte) (n int, err error)
    Write实现了标准的写接口:它将数据写入管道,直到一个或多个读取端消耗完所有数据或读取端关闭为止。如果以错误关闭读取端,则该错误将作为ERR返回;否则ERR将为ErrClosedPipe。
  2. func (w *PipeWriter) Close() error
    Close关闭写入器;关闭后如果对管道的读取端进行读取操作,就会返回(0, EOF)。
  3. func (w *PipeWriter) CloseWithError(err error) error
    CloseWithError类似Close方法,但将调用Read时返回的错误改为err。

注:以上两个结构体PipeWriter与PipeReader是结合使用的需要用Pipe()方法进行创建。

4.7.4 SectionReader

go 复制代码
type SectionReader struct {
    // contains filtered or unexported fields
}

SectionReader在ReaderAt的基础上实现了Read,Seek和ReadAt。

具体实现方法有:

  1. func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader
    结构体SectionReader的创建方法
    NewSectionReader返回一个SectionReader,它从r开始读取,偏移量为off,并在n个字节后以EOF停止。
  2. func (s *SectionReader) Read(p []byte) (n int, err error)
    实现了接口Reader的Read方法
  3. func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error)
    实现了接口ReaderAt的ReadAt方法
  4. func (s *SectionReader) Seek(offset int64, whence int) (int64, error)
    实现了接口Seeker的Seek方法
    func (*SectionReader) Size
  5. func (s *SectionReader) Size() int64
    Size返回以字节为单位的片段大小。

4.8 供外部调用的函数

4.8.1 Copy

go 复制代码
func Copy(dst Writer, src Reader) (written int64, err error)

将副本从src复制到dst,直到在src上达到EOF或发生错误。它返回复制的字节数和复制时遇到的第一个错误(如果有)。 成功的复制将返回err == nil而不是err == EOF。因为复制被定义为从src读取直到EOF,所以它不会将读取的EOF视为要报告的错误。 如果src实现WriterTo接口,则通过调用src.WriteTo(dst)实现该副本。否则,如果dst实现了ReaderFrom接口,则通过调用dst.ReadFrom(src)实现该副本。

官方示例:

go 复制代码
package main

import (
	"io"
	"log"
	"os"
	"strings"
)

func main() {
	r := strings.NewReader("some io.Reader stream to be read\n")

	if _, err := io.Copy(os.Stdout, r); err != nil {
		// os.Stdout将内容输出到控制台
		log.Fatal(err)
		// log.Fatal函数完成:
		// 1. 打印输出err
		// 2. 退出应用程序
	}

}

4.8.2 CopyBuffer

go 复制代码
func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error)

CopyBuffer与Copy相同,区别在于CopyBuffer逐步遍历提供的缓冲区(如果需要),而不是分配临时缓冲区。如果buf为nil,则分配一个;如果长度为零,则CopyBuffer会panic报错。 如果src实现WriterTo或dst实现ReaderFrom,则buf将不用于执行复制。

官方示例:

go 复制代码
package main

import (
	"io"
	"log"
	"os"
	"strings"
)

func main() {
	r1 := strings.NewReader("first reader\n")
	r2 := strings.NewReader("second reader\n")
	buf := make([]byte, 8)

	// buf is used here...
	if _, err := io.CopyBuffer(os.Stdout, r1, buf); err != nil {
		log.Fatal(err)
	}

	// ... reused here also. No need to allocate an extra buffer.
	if _, err := io.CopyBuffer(os.Stdout, r2, buf); err != nil {
		log.Fatal(err)
	}

}

4.8.3 CopyN

go 复制代码
func CopyN(dst Writer, src Reader, n int64) (written int64, err error)

CopyN将n个字节(或直到出错)从src复制到dst。它返回复制的字节数以及复制时遇到的最早错误。返回时,只有err == nil时,writte == n。 如果dst实现了ReaderFrom接口,则使用该接口实现副本。

官方示例:

go 复制代码
package main

import (
	"io"
	"log"
	"os"
	"strings"
)

func main() {
	r := strings.NewReader("some io.Reader stream to be read")

	if _, err := io.CopyN(os.Stdout, r, 4); err != nil {
		log.Fatal(err)
	}

}

运行结果:

some

4.8.4 LimitReader

go 复制代码
func LimitReader(r Reader, n int64) Reader

LimitedReader从r读取,但将返回的数据量限制为n个字节。每次读取更新n以标记剩余可以读取的字节数。Read在n<=0时或基础r返回EOF时返回EOF。

官方示例:

go 复制代码
package main

import (
	"io"
	"log"
	"os"
	"strings"
)

func main() {
	r := strings.NewReader("some io.Reader stream to be read\n")
	lr := io.LimitReader(r, 4)

	if _, err := io.Copy(os.Stdout, lr); err != nil {
		log.Fatal(err)
	}

}

运行结果:

some

4.8.5 MultiReader

go 复制代码
func MultiReader(readers ...Reader) Reader

MultiReader返回一个Reader,它是所提供的输入阅读器的逻辑串联。它们被顺序读取。一旦所有输入均返回EOF,读取将返回EOF。如果任何读取器返回非零,非EOF错误,则Read将返回该错误。

官方示例:

go 复制代码
package main

import (
	"io"
	"log"
	"os"
	"strings"
)

func main() {
	r1 := strings.NewReader("first reader ")
	r2 := strings.NewReader("second reader ")
	r3 := strings.NewReader("third reader\n")
	r := io.MultiReader(r1, r2, r3)

	if _, err := io.Copy(os.Stdout, r); err != nil {
		log.Fatal(err)
	}

}

运行结果:

first reader second reader third reader

4.8.6 MultiWriter

go 复制代码
func MultiWriter(writers ...Writer) Writer

MultiWriter创建一个Writers,将其写入复制到所有提供的写入器中,类似于Unix tee(1)命令。 每个写入一次写入每个列出的写入器。如果列出的写程序返回错误,则整个写操作将停止并返回错误;它不会在列表中继续下去。

官方示例:

go 复制代码
package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"strings"
)

func main() {
	r := strings.NewReader("some io.Reader stream to be read\n")

	var buf1, buf2 bytes.Buffer
	w := io.MultiWriter(&buf1, &buf2)

	if _, err := io.Copy(w, r); err != nil {
		log.Fatal(err)
	}

	fmt.Print(buf1.String())
	fmt.Print(buf2.String())

}

4.8.7 Pipe

go 复制代码
func Pipe() (*PipeReader, *PipeWriter)

Pipe创建一个同步的内存管道。

可用于连接期望io.Reader的代码和期望io.Writer的代码。

管道上的读和写是一对一匹配的,除非需要多次读取才能使用单次写入。也就是说,每次对PipeWriter的写入都将阻塞,直到它满足从PipeReader读取的一个或多个读取,这些读取会完全消耗已写入的数据。

数据直接从Write复制到相应的Read (或Reads);没有内部缓冲。

对读的并行调用和对写的并行调用也是安全的:单个调用将按顺序执行。

官方示例:

go 复制代码
package main

import (
	"fmt"
	"io"
	"log"
	"os"
)

func main() {
	r, w := io.Pipe()

	go func() {
		fmt.Fprint(w, "some io.Reader stream to be read\n")
		w.Close()
	}()

	if _, err := io.Copy(os.Stdout, r); err != nil {
		log.Fatal(err)
	}

}

运行结果

some io.Reader stream to be read

4.8.8 ReadAll

go 复制代码
func ReadAll(r Reader) ([]byte, error)

ReadAll从r读取,直到出现错误或EOF,并返回其读取的数据。成功的调用返回errnil,而不是errEOF。由于ReadAll定义为从src读取直到EOF,因此它不会将读取的EOF视为要报告的错误。

官方示例:

go 复制代码
package main

import (
	"fmt"
	"io"
	"log"
	"strings"
)

func main() {
	r := strings.NewReader("Go is a general-purpose language designed with systems programming in mind.")

	b, err := io.ReadAll(r)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%s", b)

}

4.8.9 ReadAtLeast

go 复制代码
func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)

ReadAtLeast从r读取到buf,直到它至少读取了min字节。它返回复制的字节数n,如果读取的字节数少则返回错误。仅当未读取任何字节时,错误才是EOF。如果在读取少于最小字节后发生EOF,则ReadAtLeast返回ErrUnexpectedEOF。如果min大于buf的长度,则ReadAtLeast返回ErrShortBuffer。返回时,当且仅当err == nil时,n> = min。

官方示例:

go 复制代码
import (
	"fmt"
	"io"
	"log"
	"strings"
)

func main() {
	r := strings.NewReader("some io.Reader stream to be read\n")

	buf := make([]byte, 14)
	if _, err := io.ReadAtLeast(r, buf, 4); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", buf)

	// buffer smaller than minimal read size.
	shortBuf := make([]byte, 3)
	if _, err := io.ReadAtLeast(r, shortBuf, 4); err != nil {
		fmt.Println("error:", err)
	}

	// minimal read size bigger than io.Reader stream
	longBuf := make([]byte, 64)
	if _, err := io.ReadAtLeast(r, longBuf, 64); err != nil {
		fmt.Println("error:", err)
	}

}

4.8.10 ReadFull

go 复制代码
func ReadFull(r Reader, buf []byte) (n int, err error)

ReadFull将r中的len(buf)个字节准确地读取到buf中。它返回复制的字节数,如果读取的字节数少则返回错误。仅当未读取任何字节时,错误才是EOF。如果在读取了一些但不是全部字节后发生EOF,则ReadFull返回ErrUnexpectedEOF。返回时,当且仅当err == nil时,n == len(buf)。

官方示例:

go 复制代码
package main

import (
	"fmt"
	"io"
	"log"
	"strings"
)

func main() {
	r := strings.NewReader("some io.Reader stream to be read\n")

	buf := make([]byte, 4)
	if _, err := io.ReadFull(r, buf); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", buf)

	// minimal read size bigger than io.Reader stream
	longBuf := make([]byte, 64)
	if _, err := io.ReadFull(r, longBuf); err != nil {
		fmt.Println("error:", err)
	}

}

4.8.11 SectionReader

SectionReader在ReaderAt的基础上实现了Read,Seek和ReadAt。

具体实现方法有:

  1. func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader
    结构体SectionReader的创建方法
    NewSectionReader返回一个SectionReader,它从r开始读取,偏移量为off,并在n个字节后以EOF停止。
  2. func (s *SectionReader) Read(p []byte) (n int, err error)
    实现了接口Reader的Read方法
  3. func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error)
    实现了接口ReaderAt的ReadAt方法
  4. func (s *SectionReader) Seek(offset int64, whence int) (int64, error)
    实现了接口Seeker的Seek方法
  5. func (s *SectionReader) Size() int64
    Size返回以字节为单位的片段大小。
4.8.11.1 NewSectionReader
go 复制代码
func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader

NewSectionReader返回一个SectionReader,它从r开始读取偏移量off,并在n个字节后以EOF停止。

官方示例:

go 复制代码
package main

import (
	"io"
	"log"
	"os"
	"strings"
)

func main() {
	r := strings.NewReader("some io.Reader stream to be read\n")
	s := io.NewSectionReader(r, 5, 17)

	if _, err := io.Copy(os.Stdout, s); err != nil {
		log.Fatal(err)
	}

}
4.8.11.2 SectionReader.Read
go 复制代码
func (s *SectionReader) Read(p []byte) (n int, err error)

实现了接口Reader的Read方法

官方示例:

go 复制代码
import (
	"fmt"
	"io"
	"log"
	"strings"
)

func main() {
	r := strings.NewReader("some io.Reader stream to be read\n")
	s := io.NewSectionReader(r, 5, 17)

	buf := make([]byte, 9)
	if _, err := s.Read(buf); err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%s\n", buf)

}
4.8.11.3 SectionReader.ReadAt
go 复制代码
func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error)

实现了接口ReaderAt的ReadAt方法

官方示例:

go 复制代码
package main

import (
	"fmt"
	"io"
	"log"
	"strings"
)

func main() {
	r := strings.NewReader("some io.Reader stream to be read\n")
	s := io.NewSectionReader(r, 5, 17)

	buf := make([]byte, 6)
	if _, err := s.ReadAt(buf, 10); err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%s\n", buf)

}
4.8.11.4 SectionReader.Seek
go 复制代码
func (s *SectionReader) Seek(offset int64, whence int) (int64, error)

实现了接口Seeker的Seek方法

官方示例:

go 复制代码
package main

import (
	"io"
	"log"
	"os"
	"strings"
)

func main() {
	r := strings.NewReader("some io.Reader stream to be read\n")
	s := io.NewSectionReader(r, 5, 17)

	if _, err := s.Seek(10, io.SeekStart); err != nil {
		log.Fatal(err)
	}

	if _, err := io.Copy(os.Stdout, s); err != nil {
		log.Fatal(err)
	}

}

可以看的出来SectionReader是根据ReaderAt实现的,而非Seeker,虽然两者的效果很像,但是ReaderAt读取内容是无视Seeker偏移量的。且在读取数据大小上ReadAt是要比Read严格的,同样的Bytes在Read上即使设大了也会没事,但在ReadAt会报错。

4.8.11.5 SectionReader.Size
go 复制代码
func (s *SectionReader) Size() int64

Size返回以字节为单位的片段大小。

官方示例:

go 复制代码
package main

import (
	"fmt"
	"io"
	"strings"
)

func main() {
	r := strings.NewReader("some io.Reader stream to be read\n")
	s := io.NewSectionReader(r, 5, 17)

	fmt.Println(s.Size())

}

4.8.12 TeeReader

go 复制代码
func TeeReader(r Reader, w Writer) Reader

TeeReader返回一个Reader,该Reader向w写入从r读取的内容。通过r执行的所有r读取均与对w的相应写入匹配。没有内部缓冲-写入必须在读取完成之前完成。写入时遇到的任何错误均报告为读取错误。

官方示例:

go 复制代码
package main

import (
	"io"
	"log"
	"os"
	"strings"
)

func main() {
	var r io.Reader = strings.NewReader("some io.Reader stream to be read\n")

	r = io.TeeReader(r, os.Stdout)

	// Everything read from r will be copied to stdout.
	if _, err := io.ReadAll(r); err != nil {
		log.Fatal(err)
	}

}

4.8.13 WriteString

go 复制代码
func WriteString(w Writer, s string) (n int, err error)

WriteString将字符串s的内容写入w,w接受字节片。如果w实现StringWriter,则直接调用其WriteString方法。否则,w.Write只调用一次。

官方示例:

go 复制代码
package main

import (
	"io"
	"log"
	"os"
)

func main() {
	if _, err := io.WriteString(os.Stdout, "Hello World"); err != nil {
		log.Fatal(err)
	}

}
相关推荐
重生之绝世牛码1 小时前
Java设计模式 —— 【行为型模式】命令模式(Command Pattern) 详解
java·大数据·开发语言·设计模式·命令模式·设计原则
大丈夫立于天地间2 小时前
OSPF - 1类LSA(Router-LSA)
网络·网络协议·学习·信息与通信
晚风_END2 小时前
node.js|浏览器插件|Open-Multiple-URLs的部署和使用,实现一键打开多个URL的强大工具
服务器·开发语言·数据库·node.js·dubbo
_周游4 小时前
【C语言】_指针与数组
c语言·开发语言
寻找优秀的自己4 小时前
WebSocket 设计思路
网络·websocket·网络协议·golang
SyntaxSage5 小时前
Scala语言的数据库交互
开发语言·后端·golang
疯狂小料5 小时前
Python3刷算法来呀,贪心系列题单
开发语言·python·算法
码力全開5 小时前
C 语言奇幻之旅 - 第14篇:C 语言高级主题
服务器·c语言·开发语言·人工智能·算法
lsx2024065 小时前
PHP Array:精通数组操作
开发语言
Chrikk5 小时前
NCCL学习笔记-概念全解
笔记·学习