go io 包再学习

一些我不怎么熟悉的接口和方法的总结

接口

io.Seeker

1
2
3
type Seeker interface {
Seek(offset int64, whence int) (ret int64, err error)
}

第一个参数是偏移量,第二个参数有几个选项,如下

1
2
3
4
5
6
// Seek whence values.
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
)

strings.NewReader 实现了该接口,所以使用它来写 demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func main() {
r := strings.NewReader("abcedfg")
readBuf := make([]byte, 2)

_, _ = r.Seek(2, io.SeekStart)
_, _ = r.Read(readBuf)
fmt.Println(string(readBuf)) // ce

_, _ = r.Seek(1, io.SeekCurrent)
_, _ = r.Read(readBuf)
fmt.Println(string(readBuf)) // fg

_, _ = r.Seek(1, io.SeekStart)
_, _ = r.Read(readBuf)
fmt.Println(string(readBuf)) // bc

_, _ = r.Seek(-2, io.SeekEnd)
_, _ = r.Read(readBuf)
fmt.Println(string(readBuf)) // fg
}

工具方法

io.LimitedReader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type LimitedReader struct {
R Reader // 数据源
N int64 // 最多读多少数据
}

func (l *LimitedReader) Read(p []byte) (n int, err error) {
if l.N <= 0 {
return 0, EOF
}
if int64(len(p)) > l.N {
p = p[0:l.N]
}
n, err = l.R.Read(p)
l.N -= int64(n)
return
}

用法:

当 N 比数据总量大时是全部读取的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func main() {
str := "abcdefghijklmnop"
r := io.LimitReader(bytes.NewReader([]byte(str)), int64(len(str)+5))
for {
buf := make([]byte, 5)
n, err := r.Read(buf)
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
fmt.Println(string(buf[:n]))
}
}
/*
abcde
fghij
klmno
p
*/

当把 io.LimitReader 的第二个参数改成小于字符串长度时,将会仅读取指定长度的字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func main() {
str := "abcdefghijklmnop"
r := io.LimitReader(bytes.NewReader([]byte(str)), 3)
for {
buf := make([]byte, 10)
n, err := r.Read(buf)
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
fmt.Println(string(buf[:n]))
}
}
/*
abc
*/

io.SectionReader

实现了如下接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Reader interface {
Read(p []byte) (n int, err error)
}

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

type ReadSeeker interface {
Reader
Seeker
}

type ReaderAt interface {
ReadAt(p []byte, off int64) (n int, err error)
}

传入的输入数据需要实现 io.ReaderAt 接口

1
2
3
4
5
// NewSectionReader returns a SectionReader that reads from r
// starting at offset off and stops with EOF after n bytes.
func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader {
return &SectionReader{r, off, off, off + n}
}

使用样例

1
2
3
4
5
6
str := "abcdefg"
r := io.NewSectionReader(strings.NewReader(str), 2, 3)
_, _ = io.Copy(os.Stdout, r) // cde
fmt.Println()
_, _ = r.Seek(0, io.SeekStart)
_, _ = io.Copy(os.Stdout, r) // cde

io.teeReader

将一个 Reader 读到的数据输出到一个 Writer 中,边读边写,实现比较简单,源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func TeeReader(r Reader, w Writer) Reader {
return &teeReader{r, w}
}

type teeReader struct {
r Reader
w Writer
}

func (t *teeReader) Read(p []byte) (n int, err error) {
n, err = t.r.Read(p)
if n > 0 {
if n, err := t.w.Write(p[:n]); err != nil {
return n, err
}
}
return
}

有一个应用场景是,读取文件上传到服务器,同时计算 md5 ,当上传完成时服务器会返回一个 md5 ,然后对这两个值进行比较,验证传输的数据无误。此时就可以边读取上传数据边计算文件的 md5 值,省去再读取一遍文件的操作

1
2
3
4
5
6
7
8
func main() {
f, _ := os.Open("teereader.go")
defer f.Close()
m := md5.New()
chekcsum, _ := PostToServer(io.TeeReader(f, m))
md5Hash := hex.EncodeToString(m.Sum(nil))
Compare(md5Hash, chekcsum)
}

io.multiReader & io.multiWriter

同时从多个 reader 读数据以及同时向多个 writer 写数据(这个类似于 tee)

io.PipeReader & io.PipeWriter

使用 io.Pipe 创建


Ref

Go语言标准库

Go语言中的io.Reader和io.Writer以及它们的实现 | 鸟窝