Golang io标准库

1、常用接口

io包为I/O提供了原语的基本接口。他的主要工作是将这些原语的现有实现

在io包中最重要的两个接口:ReaderWriter接口。只要满足这个两个接口,他就是可以使用IO包的功能。

1.1Reader接口

Reader接口的定义:

type Reader interface {
    Read(p []byte) (n int,err error)
}

官网文档说明:

Read将len(p)字节读取到p中。它返回读取的直接数n,(0<=n<=len(p))以及任何遇到的嗯错误。即使Read返回的n<len(p),它也会在调用过程中占用len(p)个字节作为暂存空间。若可读取的数据不到len(p)个字节,Read会返回可用数据,而不是等待更多数据。
当Read在成功读取n>0个字节后遇到一个错误或EOF(end-of-file),它会返回读取的字节数。他可能会同时在本次的调用中返回一个non-nil错误,或在下一次的调用中返回这个错误(且n为0)。
调用者在考虑错误之前应当首先处理返回的数据。这样做可以正确地处理在读取一些字节后产生的I/O错误,同时允许EOF的出现。	

根据Go语言中关于接口和满足了接口的类型和定义,Reader接口方法集只包含一个Read方法,因此,所有实现了Read方法的类型都满足io.Reader接口,也就是说,在所有需要io.Reader的地方,可以传递实现Read()方法的类型的实例。

接口示例用法:

func ReadFrom(reader io.Reader,num int) ([]byte,error) {
    p:=make([]byte,num)
    n,err:=reader.Read(p)
    if n > 0 {
        return p[:n]
    }
    return p,err
    
}

ReadFrom函数将io.Reader作为参数,也就是说,ReadFrom可以从任意的地方读取数据,只要来源实现了io.Reader接口。

从标准输入、文件、字符串读取数据

示例:

//从标准输入读取
data,err = ReadFrom(os.Stdin,11)
//从普通文件读取,其中file是os.File的实例
data,err = ReadFrom(file,9)
//从字符床读取
data,err = ReadFrom(strings.NewReader("from string"),12)

io.EOF变量的定义:var EOF= errors.New("EOF"),是error类型。根据reader接口的说明,在n>0且数据被读完了的情况下,当返回的error有可能是EOF也有可能是nil.

1.2 Writer接口

writer接口定义:

type Writer interface {
    Write(p []byte) (n int,err error)
}

官方文档接口方法说明:

Write将len(p)个字节从p中写入到基本数据流中。它返回从p中被写入的字节数n(0<=n<=len(p)) 以及任何遇到的引起写入提前停止的错误。若Write返回的n<len(p),他就必须返回一个非nil的错误。

同样的,所有实现了Write方法的类型都实现了io.Writer接口。

在上个示例中,自己实现了一个函数接收一个io.Reader类型的参数。这里,通过标准库示例学习。

在fmt标准库中,有一组函数:Fprint/Fprintf/Fprintln,他们接收一个io.Writer类型的参数(第一个参数),也就是说他们将数据格式化输出到io.Writer中。那么,调用这组函数时,该如何传递这个参数呢?

以fmt.Fprintln为例,同时看下fmt.Println函数源码。

func Println(a ...interface{}) (n int, err error) {
    return Fpringln(os.Stdout,a...)
}

很显然,fmt.Println会将内容输出到标准输出中。

实现了io.Reader接口或io.Write接口的类型

标准库中有哪些类型实现了io.Reader或io.Writer接口?

通过上面的示例,可以知道,os.File同时实现了这两个接口。还可以看到os.Stdin/os.Stdout这样的代码,他们似乎分别实现了io.Reader/io.Writer接口。

实际上在os包中有这样的代码:

var (
    Stdin = NewFile(uinptr(syscall.Stdin),"/dev/stdin")
    Stdout = NewFile(uinptr(syscall.Stdout),"/dev/stdout")
    Stderr = NewFile(uinptr(syscall.Stderr),"/dev/stderr")
)

也就是说,Stdin/Stdout/Stderr只是三个特殊的文件类型的标识(都是os.file的实例),自然也实现了io.Reader和io.Writer。

目前,Go文档中还没有直接列出实现了某个接口的所有类型。不过,可以通过查看标准库文档,列出实现io.Reader或io.Writer接口的类型(导出的类型):

  • os.File同时实现了io.Reader和io.Writer

  • strings.Reader实现了io.Reader

  • bufio.Reader/Writer分别实现了io.Reader和io.Writer

  • bytes.Buffer同时实现了io.Reader和io.Writer

  • bytes.Reader实现了io.Reader

  • compress/gzip.Reader/Writer分别实现了io.Reader和io.Writer

  • crypto/cipher.StreamReader/StreamWriter分别实现了io.Reader和io.Writer

  • crypro/tls.Conn同时实现了io.Reader和io.Writer

  • encoding/csvReader/Writer分别实现了io.Reader和io.Writer

  • mime/multipart。Part实现了io.Reader

  • net/conn分别实现了io.Reader和io.Writer(Conn接口定义了Read/Write)

除此之外,io本身也有这两个接口的实现类型:

实现了Reader的类型:LimitReader、PipeReader、SectionReader
实现了Writer的类型:PipeWriter		

以上类型中,常用的类型有:os.File、strings.Reader、bufio.Reader/Writer、bytes.Buffer、bytes.Reader

1.3 ReaderAt和WriterAt接口

ReaderAt接口定义:

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

官方文档接口说明:

ReadAt从基本输入源的偏移量off处开始,将len(p)个字节读取到P中。它返回读取的字节数n(0<=n<=len(p))以及任何遇到的错误。
当ReadAt返回的n<len(p)时,它就会返回一个非nil的错误来解析,为什么没有返回更多的字节。在这一点上,ReadAt比Read更严格。	
即使ReadAt返回的n<len(p),它也会在调用过程中使用p的全部作为暂存空间。若可读取的数据不到len(p)字节,ReadAt就会阻塞,直到所有数据都可用或一个错误发生。在这一点上ReadAt不同于Read。
若n=len(p)个字节从输入源的结尾处由ReadAt返回,Read可能返回err==EOF或者err==nil。
若ReadAt携带一个偏移量从输入源读取,ReadAt应当既不影响偏移量也不被它所影响。可对相同的输入源并行执行ReadAt调用。

课件,ReaderAt接口使得可以从指定偏移量处开始读取数据。

示例代码:

reader := strings.NewReader("Go中国北京")
p:=make([]byte,6)
n,err := reader.ReadAt(p,2)
if err != nil {
    panic(err)
}
fmt.Printf("%s,%d\n",p,n)


output>>>
中国,6

WriteAt接口定义:

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

官方文档说明:

WriteAt从p中将len(p)个字节写入到偏移量off处的基本数据流中。它返回从p中被写入的字节数n(0<=n<=len(p))以及遇到的引起写入提前停止的错误。若WriteAt返回的n<len(p),它就必须返回一个非nil的错误。
若WriteAt携带一个偏移量写入到目标中,WriteAt应当既不影响偏移量也不被它所影响。
若被写入的区域没有重叠,可对相同的目标并行执行WriteAt调用。

可以通过该接口将数据写入到数据流的特定偏移量之后。

示例:

file,err := os.Create("writeAt.txt")
if err != nil {
    panic(err)
}
defer file.Close()
file.WriteString("芝麻开花--补充")
n,err := file.WriteAt([]byte("节节高"),14)
if err != nil {
    panic(err)
}
fmt.Println(n)

writeAt.txt内容:芝麻开花--节节高

1.4 ReaderFrom和WriterTo接口

ReaderFrom定义:

type ReaderFrom interface {
    ReadFrom(r Reader) (n int64,err error)
}

官方文档说明:

ReadFrom从r中读取数据,知道EOF或发生错误。其返回值n为读取的字节数。除io.EOF之外,在读取过程中遇到的任何错误也讲被返回。	

注意:ReadFrom方法不会返回err == EOF

示例:

file,err := os.Open("writeAt.txt")
if err != nil {
    panic(err)
}
defer file.Close()
writer := bufio.NewWriter(os.Stdout)
writer.ReadFrom(file)
writer.Flush()

也可以通过ioutil包的ReadFile函数获取文件全部内容。其实,跟踪一下ioutil.ReadFile的源码,会发现其实也是通过ReadFrom方法实现(用的是bytes.Buffer,它实现了ReaderFrom接口)。

如果不通过ReadFrom接口做,而使用io.Reader接口,有两种思路:

1、先获取文件的大小(File的Stat方法),之后定义一个该大小的[]byte,通过Read一次性读取。

2、定义一个小的[]byte,不断的调用Read方法直到遇到EOF,将所有读取到的[]byte连接到一起。

查看bufio.Writer或strings.Buffer类型的ReadFrom方法实现,会发现,其实它们的实现和上述第二种思路类似。

WriteTo定义:

type WriteTo interface {
    WriteTo(w Writer) (n int64,err error)
}

官方文档说明:

WriteTo将数据写入w中,直到没有数据可写或发生错误。其返回值n为写入的字节数,在写入过程中遇到的而任何错误也将返回。
如果WriteTo可用,Copy函数就会使用它

其中ReadFrom和WriteTo接口的方法接收的参数是io.Reader和io.Writer类型。根据io.Reader和io.Writer接口的讲解,对该接口的使用应该可以很好的掌握。

示例:

reader := bytes.NewReader([]byte("中国北京"))
reader.WriteTo(os.Stdout)

通过io.ReaderFrom和io.WriterTo的学习,这样的需求可以使用这个两个接口:“一次性从某个地方读或写到某个地方去”

1.5 Seeker接口

接口定义:

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

官方文档说明:

Seek设置下一次Read或Write的偏移量为offset,它的解释取决于whence:0表示相对于文件的起始处,1表示相对于当前的偏移,2标识相对于其结尾处。Seek返回新的偏移量和一个错误,如果有的话。

也就是说,Seek方法是用于设置偏移量,这样可以从某个特定的位置开始操作数据流。听起来和ReaderAt/WriteAt接口有些类似,不过Seeker接口更灵活,可以更好的控制读写数据流的位置。

简单的示例代码:获取倒数第二个字符(需考虑UTF-8编码)

示例:

reader := strings.NewReader("北京欢迎你")
reader.Seek(-3,2)
r,_,_ := reader.ReadRune()
fmt.Printf("%c\n",r)

whence的值,在io包中定义了相应的常量

// 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
)

而原先os包中的常量已经被标注为Deprecated

// Deprecated: Use io.SeekStart, io.SeekCurrent, and io.SeekEnd.
const ( SEEK_SET int = 0 // seek relative to the origin of the file 
       SEEK_CUR int = 1 // seek relative to the current offset 
       SEEK_END int = 2 // seek relative to the end 
)

1.6 Close接口

接口定义:

type Closer interface {
    Close() error
}

Close()方法用于关闭数据流

文件(os.File)、归档(压缩包)、数据库连接、Socket等需要手动关闭的资源都实现了Closer接口。

实际中,经常将Close()方法的调用放在defer语句中。

2、其他接口

2.1 ByteReader和ByteWriter

通过名称大概能猜出这组接口的用途:读或写一个字节。

接口定义:

type ByteReader interface {
    ReadByte() (c byte,err error)
}

type ByteWriter interface {
    WriteByte(c byte) error
}

在标准库中,有如下类型实现了io.ByteReader或io.ByteWriter:

  • bufio.Reader/Writer分别实现了io.ByteReader和io.ByteWriter
  • bytes.Buffer同时实现了io.ByteReader和io.ByteWriter
  • bytes.Reader实现了io.ByteReader
  • strings.Reader实现了io.ByteReader

示例:

var ch byte
fmt.Scanf("%c\n",&ch)

buffer := new(bytes.Buffer)
err := buffer.WriteByte(ch)
if err == nil {
    fmt.Println("写入一个字节成功!准备读取该字节。。。。")
    newCh,_ := buffer.ReadByte()
    fmt.Printf("读取的字节:%c\n",newCh)
}else{
    fmt.Println("写入错误")
}

程序从标准输入接收一个字节(ASCII字符),调用buffer的WriteByte将该字节写入buffer中,之后通过ReadByte读取该字节。

一般,不会使用bytes.Buffer来一次读取或写入一个字节。那么,这两个接口有哪些用处呢?

在标准库encode/binary中,实现google-ProtoBuf中的Varints读取,ReadVarint就需要一个io.ByteReader类型的参数,也就是说,它需要一个字节一个字节的读取。

在标准库image/jpeg中,Encode函数内部实现使用了ByteWriter写入一个字节。

在Go源码src/pkg中搜索io.ByteReader或io.ByteWriter,这两个接口在二进制数据或归档压缩时用的比较多。

2.2 ByteScanner、RuneReader和RuneScanner

这三个接口放在一起,考虑到与ByteReader相关相应

ByteScanner接口定义:

type ByteScanner interface {
    ByteReader
    UnreadByte() error
}

它内嵌了ByteReader接口(可以理解为继承了ByteReader接口),UnreadByte方法的意思是:将上一次ReadByte的字节还原,使得再次调用ReadByte返回的结果和上一次调用相同,也就是说,UnreadByte是重置上一次的ReadByte。

注意:UnreadByte调用之前必须调用了ReadByte,且不能连续调用UnreadByte。即:

buffer := bytes.NewBuffer([]byte{'a','b'})
err := buffer.UnreadByte()

buffer := bytes.NewBuffer([]byte{'a','b'})
buffer.ReadByte()
err := buffer.UnreadByte()
err = buffer.UnreadByte()

err都非nil,错误为bytes.Buffer: UnreadByte: previous operation was not a successful read

RuneReader接口和ByteReader类似,只是ReadRune方法读取单个UTF-8字符,返回其rune和该字符占用的字节数。该接口在regexp包有用到。

问题:

strings.Index("行业交流群","交流")返回的单字节字符的位置:6。但是想要的是unicode字符的位置2。

借助utf8的RuneCountlnString函数,实现代码:

//strings.Index的UTF-8版本
//即Utf8Index("行业交流群","交流")返回的是4,而不是strings.Index的8
func Utf8Index(str,substr string) int {
    index := strings.Index(str,substr)
    if index < 0 {
        return -1
    }
    return utf8.RuneCountInString(str[:index])
}

2.3 ReadCloser、ReadSeeker、ReadWriteCloser、ReadWriteSeeker、ReadWriter、WriteCloser、WriteSeeker

这些接口是上面介绍的接口的两个或三个组合而成的新接口。

例如:

type ReadWriter interface {
    Reader
    Writer
}

这是Reader接口和Writer接口的简单组合(内嵌)

这些接口的作用是:有些时候同时需要某两个接口的所有功能,即必须同时实现了某两个接口的类型才能够被传入使用。可见,io包中有大量的“小接口”,这样方便组合为“大接口”。

2.4 SectionReader类型

SectionReader是一个struct(没有任何导出的字段),实现了Read,Seek和ReadAt,同时,内嵌了ReaderAt接口。

结构体定义:

type SectionReader struct {
    r     ReaderAt  //该类型最终的Read/ReadAt 最终都是通过r的ReadAt实现
    base  int64		//NewSectionReader会将base设置off
    off   int64		//从r中的off偏移处开始读取数据
    limit int64		//limit - off = SectionReader流的长度
}

该类型是读取数据流中部分数据

func NewSectionReader(r ReaderAt,off int64, n int64) *SectionReader		

NewSectionReader返回一个SectionReader,它从r中的偏移量off处读取n个字节后以EOF停止。

也就是说,SectionReader只是内部(内嵌)ReaderAt表示的数据流的一部分:从off开始后的n个字节。

这个类型的作用是:方便重复操作某一段(section)数据流;或者同时需要ReadAt和Seek的功能。

该类型在标准库zip归档访问会涉及

2.5 LimitedReader类型

结构定义:

type LimitedReader struct {
    R Reader //underlying reader 最终的读取操作通过R.Read完成
    N int64  //max bytes reaining
}

官方文档说明:

从R读取但将返回的数据量限定为N字节。每调用一次Read都将更新N来反应新的剩余数量

也就是说,最多只能返回N字节数据。

LimitedReader只实现了Read方法(Reader接口)

示例:

content := "This Is LimitReader Example"
reader := strings.NewReader(content)
limitReader := &io.LimitedReader{R:reader,N:8}
for limitReader.N > 0 {
    tmp := make([]byte,2)
    limitReader.Read(tmp)
    fmt.Printf("%s",tmp)
}

output>>>
This Is

通过该类型可以达到只允许读取一定长度数据的目的。

在io包中,LimitReader函数的实现其实就是调用LimitedReader:

func LimitReader(r Reader,n int64) Reader{ return &LimitedReader{r,n} }

2.6 PipeReader和PipeWriter类型

PipeReader(一个没有任何导出字段的struct)是管道的读取端。它实现了io.Reader和io.Closer接口。

结构定义:

type PipeReader struct {
    p *pipe
}

关于PipeReader.Read方法的说明:从管道中读取数据。该方法会堵塞,直到管道写入端开始写入数据或写入端被关闭。如果写入端关闭时带有error(即调用CloseWithError关闭),该Read返回的err就是写入端传递的error;否则err为EOF。

PipeWither(一个没有任何导出字段的struct)是管道的写入端。它实现了io.Writer和io.Closer接口。

结构定义:

type PipeWriter struct {
    p *pipe
}

关于PipeWriter.Write方法的说明:写数据到管道中。该方法会堵塞,直到管道读取端读完所有数据或读取端被关闭。如果读取端关闭时带有error(即调用CloseWithError关闭),该Write返回的err就是读取端传递的error;否则err为ErrClosedPipe。

示例:

func main() {
	pipeReader, pipeWriter := io.Pipe()
	go PipeWrite(pipeWriter)
	go PipeRead(pipeReader)
	time.Sleep(30 * time.Second)
}

func PipeWrite(writer *io.PipeWriter) {
	data := []byte("学无止境")
	for i := 0; i < 3; i++ {
		n, err := writer.Write(data)
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Printf("写入字节 %d\n", n)
	}
	writer.CloseWithError(errors.New("写入时关闭"))
}

func PipeRead(reader *io.PipeReader) {
	buf := make([]byte, 128)
	for {
		fmt.Println("接口端开始阻塞5s...")
		time.Sleep(time.Second * 5)
		fmt.Println("接收端开始接受")
		n, err := reader.Read(buf)
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Printf("收到字节:%d\nbuf内容:%s\n", n, buf)
	}
}

io.Pipe()用于创建一个同步的内存管道(synchronous in-memory pipe),函数签名:

func Pipe() (*PipeReader,*PipeWriter)

它将io.Reader连接io.Writer。一端的读取匹配另一端的写入,直到在这两段之间复制数据;它没有内部缓存。它对于并行调用Read和Write以及其他函数或Close来说都是安全的。一旦等待的I/O结束,Close就会完成。并行调用Read或并行调用Write也同样安全:同种类的调用将按顺序进行控制。

正因为同步,因此不能再一个goroutine中进行读和写。

管道的close方法(非CloseWithError时),err会被置为EOF。

2.7 Copy和CopyN函数

Copy函数签名:

func Copy(dst Writer,src Reader) (written int64,err error)	

官方文档说明:

Copy将src复制到dst,直到在src上到达EOF或发生错误。它返回复制的字节数,如果有错误的话,还会返回在复制时遇到的第一个错误。
成功的Copy返回 err==nil,而非 err==EOF。由于Copy被定义为从src读取直到EOF为止,因此它不会将来自Read的EOF当做错误来报告。
若dst实现了ReaderFrom接口,其复制操作可通过调用dst.ReadFrom(src)实现。此外,若src实现了WriterTo接口,其复制操作可通过调用src.WriteTo(dst)实现。		

示例:

io.Copy(os.Stdout,strings.NewReader("学无止境"))

output>>>
学无止境

也可以这样:

package main

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

func main() {
    io.Copy(os.Stdout,os.Stdin)
    fmt.Println("Got EOF -- bye")
}

CopyN函数的签名:

func CopyN(dst Writer,src Reader,n int64) (written int64,err error)

官方文档说明:

CopyN将n个字节(或到一个error)从src复制到dst。它返回赋值的字节数以及在复制时遇到的最早的报错。当 err==nil时,written==n。
若dst实现了ReaderFrom接口,复制操作也就会使用它来实现。

示例:

io.CopyN(os.Stdout,strings.NewReader("学无止境"),6)

output>>>
学无

2.8 ReadAtLeast和ReadFull函数

ReadAtLeast函数签名:

func ReadAtLeast(r Reader,buf []byte,min int)(n int,err error)

官方文档说明:

ReadAtLeast将r读取到buf中,直到读了最少min个字节为止。它返回复制的字节数,如果读取的字节较少,还会返回一个错误。若没有读取到字节,错误就只是EOF。如果一个EOF发生在读取少于min个字节之后,ReadAtLeast就会返回ErrUnexpectedEOF。若min大于buf的长度,ReadAtLeast就会返回ErrShortBuffer。对于返回值,当 err==nil时,才有n>=min。

一般可能不会用到这个函数,使用是注意返回的error判断

ReadFull函数签名:

func ReadFull(r Reader,buf []byte) (n int,err error)

官方文档说明:

ReadFull精确地从r中将len(buf)个字节读取到buf中。它返回复制的字节数,如果读取的字节较少,还会返回一个错误。若没有读取到字节,错误就只是EOF。如果一个EOF发生在读取了一些但不是所有的字节后,ReadFull就会返回ErrUnexpectedEOF。对于返回值,当 err==nil时,才有 n==len(buf)。

注意:该函数与ReadAtLeast的区别:ReadFull将buf读满;而ReadAtLeast是最少读取min个字节。

2.9 WriteString函数

这是为了方便写入string类型提供的函数

函数签名:

func WriteString(w Write,s string) (n int,err error)

官方文档说明:

WriteString将s的内容写入w中,当w实现了WriteString方法时,会直接调用该方法,否则执行w.Write([]byte(s))只会被调用一次。

3.0 MultiReader和MultiWriter函数

这两个函数的定义分别是:

func MultiReader(readers ...Reader) Reader
func MultiWriter(writers ...Writer) Writer

它们接收多个Reader或Writer,返回一个Reader或Writer。我们可以猜想到这两个函数就是操作多个Reader或Writer就像操作一个。

事实上,在io包中定义了两个非到处类型:multiReader和multiWriter,它们分别实现了io.Reader和io.Writer接口。

类型定义:

type multiReader struct {
    readers []Reader
}
type multiWriter struct {
    writers []Writer
}

两种类型对应的实现方法(Read和Write)方法的使用

MultiReader的使用:

readers := []io.Reader{
    strings.NewReader("from strings reader"),
    bytes.NewBufferString("from bytes buffer"),
}
reader := io.MultiReader(readers...)
data := make([]byte, 0, 128)
buf := make([]byte, 10)
for n, err := reader.Read(buf); err != io.EOF; n, err = reader.Read(buf) {
    if err != nil {
        panic(err)
    }
    fmt.Println(n)
    fmt.Println(string(buf))
    data = append(data, buf[:n]...)
}
fmt.Printf("%s\n", data)

output>>>
from strings readerfrom bytes buffer

代码中首先构造了一个io.Reader的slice,由strings.Reader和bytes.Buffer两个示例组成,然后通过multiReader得到新的Reader,循环读取新的Reader中的内容。从输出结果可以看到,第一次调用Reader的Read方法获取得到的是slice中第一个元素的内容。。。也就是说,multiReader只是逻辑上将多个Reader组合起来,并不能通过调用一次Read方法获取所有Reader的内容,在所有的Reader内容都被读取完后,Reader会返回EOF。

MultiWriter的使用:

file, err := os.Create("multiWriter.txt")
if err != nil {
    panic(err)
}
defer file.Close()
writers := []io.Writer{
    file,
    os.Stdout,
}
writer := io.MultiWriter(writers...)
writer.Write([]byte("学无止境"))

执行后生成multiWriter.txt文件,同时在文件和屏幕中都输出“学无止境”。与unix的tee命令类似

3.1 TeeReader函数

函数签名:

func TeeReader(r Reader,w Writer) Reader

官方文档说明:

TeeReader返回一个Reader,它将从r中读到的数据写入w中。所有经由它处理的从r的读取都匹配于对应的对w的写入。它没有内部缓存,即写入必须在读取完成前完成。任何写入时遇到的错误都讲作为读取错误返回。

也就是说,通过Reader读取内容后,会自动写入到Writer中去。

示例:

reader := io.TeeReader(strings.NewReader("学无止境"),os.Stdout)
reader.Read(make([]byte,12))

output>>>
学无止境

这功能的实现简单,无非是在Read完后执行Write

posted @ 2021-10-27 21:14  自己有自己的调调、  阅读(585)  评论(0编辑  收藏  举报