• 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
    ",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
    ",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
    ",&ch)
    
    buffer := new(bytes.Buffer)
    err := buffer.WriteByte(ch)
    if err == nil {
        fmt.Println("写入一个字节成功!准备读取该字节。。。。")
        newCh,_ := buffer.ReadByte()
        fmt.Printf("读取的字节:%c
    ",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)
    	}
    	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
    buf内容:%s
    ", 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
    ", 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

  • 相关阅读:
    linux下进程的实际用户ID(有效组)和有效用户ID(有效组ID)
    ubuntu下软件中心闪退问题解决
    LINUX(UNIX)文件I/O学习
    ubunut下桌面文件路径修改
    ubuntu下设置jdk/jre环境
    Fire net
    JavaScript 自己写一个 replaceAll() 函数
    Canvas 绘制一个像素风电子时钟
    Python3 笔记01:求两数之和
    尝试笔记 01 之 CSS 边角上的标签
  • 原文地址:https://www.cnblogs.com/remixnameless/p/15472989.html
Copyright © 2020-2023  润新知