Reader
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
The Read method on io.Reader is more interesting.
Rather than return data through a return parameter, a slice input parameter is passed in to the implementation and modified.
Up to len(p) bytes will be written into the slice.
The method returns the number of bytes written. This might seem a little strange. You might expect this:
type NotHowReaderIsDefined interface {
Read() (p []byte, err error)
}
下面,我们通过一个例子,来看下,为甚Reader的Read方法,需要传入一个slice,不是返回一个slice
func countLetters(r io.Reader) (map[string]int, error) {
buf := make([]byte, 1024)
out := map[string]int{}
for {
n, err := r.Read(buf)
for _, b := range buf[:n] {
if (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z') {
out[string(b)]++
}
}
if err == io.EOF {
return out, nil
}
if err != nil {
return nil, err
}
}
}
There are three things to note.
First, we create our buffer once and re-use it on every call to r.Read.
This allows us to use a single memory allocation to read from a potentially large data source.
If the Read method was written to return a []byte, it would require a new allocation on every single call.
Each allocation would end up on the heap, which would make quite a lot of work for the garbage collector.
Second, we use the n value returned from r.Read to know how many bytes were written to the buffer and iterate over a sub-slice of our buf slice, processing the data that was read.
Finally, we know that we’re done reading from r when the error returned from r.Read is io.EOF.
This error is a bit odd, in that it isn’t really an error. It indicates that there’s nothing left to read from the io.Reader.
When io.EOF is returned, we are finished processing and return our result.
创建一个reader
func main() {
s := "zhangsan"
sr :=strings.NewReader(s)
counts,err := countLetters(sr)
if err != nil {
fmt.Println(err)
}
fmt.Println(counts)
}
GzipReader
decorator pattern:
package main
import (
"compress/gzip"
"fmt"
"io"
"os"
)
func main() {
r, closer, err := buildGzipReader("E:\\test\\name.txt.gz")
if err != nil {
fmt.Println(err)
return
}
defer closer()
counts, err := countLetters(r)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(counts)
}
func countLetters(r io.Reader) (map[string]int, error) {
buf := make([]byte, 1024)
out := map[string]int{}
for {
n, err := r.Read(buf)
for _, b := range buf[:n] {
if (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z') {
out[string(b)]++
}
}
if err == io.EOF {
return out, nil
}
if err != nil {
return nil, err
}
}
}
func buildGzipReader(filename string) (*gzip.Reader, func(), error) {
file, err := os.Open(filename)
if err != nil {
return nil, nil, err
}
gr, err := gzip.NewReader(file)
if err != nil {
return nil, nil, err
}
return gr, func() {
gr.Close()
file.Close()
}, nil
}
Closer
type Closer interface {
Close() error
}
The io.Closer
interface is implemented by types like os.File
that need to do cleanup when reading or writing is complete.Usually, Close is called via a defer
:
f, err := os.Open(fileName)
if err != nil {
return nil, err
}
defer f.Close() // use f
Seeker
type Seeker interface {
Seek(offset int64, whence int) (int64, error)
}
// Seeker 接口封装了基本的 Seek 方法
// Seek 设置下一次读写操作的指针位置,每次的读写操作都是从指针位置开始的
// whence 的含义:
// 如果 whence 为 0:表示从数据的开头开始移动指针
// 如果 whence 为 1:表示从数据的当前指针位置开始移动指针
// 如果 whence 为 2:表示从数据的尾部开始移动指针
// offset 是指针移动的偏移量
// 返回,移动后的指针位置, 和移动过程中遇到的任何错误
// Seeker 用来移动数据的读写指针
官方注释:Seek设置下一次读/写的位置。offset为相对偏移量,而whence决定相对位置:0为相对文件开头,1为相对当前位置,2为相对文件结尾。它返回新的偏移量(相对开头)和可能的错误。
package main
import (
"compress/gzip"
"fmt"
"io"
"log"
"os"
)
func main() {
tSeek_1()
}
func tSeek_1() {
// 0123456789
f, err := os.OpenFile(`E:\\test\\1.txt`, os.O_RDWR, os.ModePerm)
if err != nil {
log.Fatal(err)
}
defer f.Close()
end, err := f.Seek(2, io.SeekEnd)
if err != nil {
log.Fatal(err)
}
fmt.Println("end: ", end) // end: 10
// 都是含前不含后的概念
// offset是从0开始的, 可以比当前的文件内容长度大,多出的部分会用空(0)来代替
start, err := f.Seek(16, io.SeekStart)
if err != nil {
log.Fatal(err)
}
fmt.Println("start: ", start)
current, err := f.Seek(1, io.SeekCurrent)
if err != nil {
log.Fatal(err)
}
fmt.Println("current: ", current)
}
执行结果
end: 12
start: 16
current: 17
nopCloser
One of the more clever functions in ioutil demonstrates a pattern for adding a method to a Go type.
If you have a type that implements io.Reader but not io.Closer (such as strings.Reader) and need to pass it to a function that expects an io.ReadCloser, pass your io.Reader into ioutil.NopCloser and get back a type that implements io.ReadCloser.
If you look at the implementation, it’s very simple:
type nopCloser struct {
io.Reader
}
func (nopCloser) Close() error {
return nil
}
func NopCloser(r io.Reader) io.ReadCloser {
return nopCloser{r}
}
Any time you need to add additional methods to a type so that it can meet an interface, use this embedded type pattern.