Golang Reader 接口实现
尽管本文探讨的是如何实现 io.Reader 接口,但是作为实现接口的一般套路也是有意义的。在讨论接口实现的这个主题时,我发现多数文章所列举的示例都脱离的现实,比如去实现一个 Animal 接口。
首先,我们看下如何编写代码的数据接口才能满足实现 io.Reader 接口的条件。从 go 文档我们可以看到。
type Reader interface {
Read(p []byte) (n int, err error)
}
这看起来很简单,我们要做的就是去实现一个 Read 方法。本文我将实现一个 Stringer 数据结构,当调用 read 方法时,它将输出字符串。它看起来如下。
type Stringer struct {
stringer string
read bool
}
现在我们要去实现 io.Reader 接口,仅需要创建 Read 方法,接口签名是一个 slice 的 bytes 数据,返回 int 和 error 数据。
func (s Stringer) Read(p []bytes) (n int, err error) {
}
如果你之前使用过 go,你可能已经发现这段代码是无法工作的。由于它没有指针指向底层数据结构,所以我们无法跟踪它已经读取过的内容。这个问题的原因是当注入 io.ReadAll 多次调用 Read 方法时会期望在读到文件结束是收到一个错误通知。如果无法实现你的程序会耗尽内存。
我们真正想实现的是有如下函数签名的方法。
func (s *Stringer) Read(p []bytes) (n int, err error) {
}
它的问题在于,当你实现这个方法并将他作为参数传递个接收 io.Reader 类型的方法时,你会受到下面的信息。这里是完整代码的链接。
Stringer does not implement io.Reader (Read method has pointer receiver)
译注:代码片段:
package main
import "io/ioutil"
type Stringer struct {
name string
done bool
}
func (s *Stringer) Read(p []byte) (n int, err error) {
return 0, nil
}
func main() {
s := Stringer{}
ioutil.ReadAll(s)
}
解决的方法是,创建一个指针类型作为参数传入。不过它会抛出另一个错误,除非你正确实现 Reader 方法 http://play.golang.org/p/gyMcTp2ALX。
译注:代码片段:
package main
import "io/ioutil"
import "io"
type Stringer struct {
name string
done bool
}
func (s *Stringer) Read(p []byte) (n int, err error) {
return 0, io.EOF
}
func main() {
s := &Stringer{}
ioutil.ReadAll(s)
}
现在来实现真正可以工作的 Read 方法。
func (r *Reader) Read(p []byte) (n int, err error) {
if r.done {
return 0, io.EOF
}
for i, b := range []byte(r.read) {
p[i] = b
}
r.done = true
return len(r.read), nil
}
如你所见,正因为我们可以操作底层数据结构,我们才能够标记合适 read 应该停止被调用。完整的实现类似于 http://play.golang.org/p/ejpUVOx8jR。
译注:代码片段:
package main
import "io/ioutio"
import "io"
import "log"
type Reader struct {
read string
done bool
}
func NewReader(toRead string) *Reader {
return &Reader{toRead, false}
}
func (r *Reader) Read (p []byte) (n int, err error) {
if r.done {
return 0, io.EOF
}
for i, b := range []byte(r.read) {
p[i] = b
}
r.done = true
return len(r.read), nil
}
func main() {
M := NewReader ("test")
stuff, _ := ioutil.ReadAll(M)
log.Printf("%s", stuff)
}