Go deep

Golang 中 TeeReaderWriter 输出为空的问题小记

August 01, 2020 | 5 Minutes Read

是一个在工作的时候遇到的 TeeReaderWriter 为空的问题。花了一点时间解决。并且根据官方示例代码做出一定的调整,发现了一个比较有意思的现象。但是没有什么人说到这个问题。

并且,由于对 Golang 的流式处理不熟悉,途中遇到了很多的问题。

因此,来对记录一下 TeeReader 的解读。


Table of Contents


TeeReader

TeeReader 的描述

首先放出 TeeReader 的源代码。代码比较简单,如下:

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
}

官方的英文解释如下:

TeeReader returns a Reader that writes to w what it reads from r.

All reads from r performed through it are matched with corresponding writes to w. There is no internal buffering - the write must complete before the read completes.

Any error encountered while writing is reported as a read error.

第一句话对于中文语言者可能没有那么好理解,它的意思其实非常简单:

就是将 r 中的数据读出后同时写入 w 中,然后 `teeReader` 本身也是可以读取数据的,这样,就相当于复制出了一条数据流。

TeeReader 的用法

官方的源代码示例如下:

r := strings.NewReader("some io.Reader stream to be read\n")
var buf bytes.Buffer
tee := io.TeeReader(r, &buf)

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

	fmt.Printf("%s", b)
}

printall(tee)
printall(&buf)

其输出结果可得:

some io.Reader stream to be read # <= 这个是*io.teeReader
some io.Reader stream to be read # <= 这个是*bytes.Buffer

TeeReader 为什么 Writer 为空?

TeeReader 的参数是一个 reader 和一个 writer。但是为什么有时候 writer 会是空的?

这也是我在实际工作中遇到的一个问题。

这里,我们来把源代码示例的 printall 的输出顺序调整一下,如下:

//修改前的
printall(tee)
printall(&buf)

//修改过后的
printall(&buf)
printall(tee)

这个时候输出即可看到,一个输出结果,也就是:

some io.Reader stream to be read

这个时候如果使用 reflect.typeOf() 去看他们的输出类型,输出结果为:

*bytes.Buffer: *io.teeReader: some io.Reader stream to be read

因此,可以看得到这里的 bytes.Buffer 输出内容为空,但是 teeReader 是不空的。

那么为什么会这样呢?

A: 其实,这是由于 teeReader 必须要完成了过后,才会写入到 buf 当中。因此,提前写诗没有任何数据的。

这个也符合官方原文的英文解释:

TeeReader returns a Reader that writes to w what it reads from r.

希望对你有用。