Go语言之Writer 和 Reader

简介: 输入和输出Go Writer 和 Reader接口的设计遵循了Unix的输入和输出,一个程序的输出可以是另外一个程序的输入。他们的功能单一并且纯粹,这样就可以非常容易的编写程序代码,又可以通过组合的概念,让我们的程序做更多的事情。
输入和输出


Go Writer 和 Reader接口的设计遵循了Unix的输入和输出,一个程序的输出可以是另外一个程序的输入。他们的功能单一并且纯粹,这样就可以非常容易的编写程序代码,又可以通过组合的概念,让我们的程序做更多的事情。


比如我们在上一篇的Go log中,就介绍了Unix的三种输入输出输入模式,他们对应的Go语言里有专门的实现。


var (
    Stdin  = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
    Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
    Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr"))
AI 代码解读


这三种标准的输入和输出都是一个*File,而*File恰恰就是同时实现了io.Writerio.Reader这两个接口的类型,所以它们同时具备输入和输出的功能,既可以从里面读取数据,又可以往里面写入数据。


Go标准库的io包也是基于Unix这种输入和输出的理念,大部分的接口都是扩展了io.Writerio.Reader,大部分的类型也都选择地实现了io.Writerio.Reader这两个接口,然后把数据的输入和输出,抽象为流的读写。所以只要实现了这两个接口,都可以使用流的读写功能。


io.Writerio.Reader两个接口的高度抽象,让我们不用再面向具体的业务,我们只关注,是读还是写。只要我们定义的方法函数可以接收这两个接口作为参数,那么我们就可以进行流的读写,而不用关心如何读、写到哪里去,这也是面向接口编程的好处。


Reader和Writer接口


这两个高度抽象的接口,只有一个方法,也体现了Go接口设计的简洁性,只做一件事。


// Writer is the interface that wraps the basic Write method.//// Write writes len(p) bytes from p to the underlying data stream.// It returns the number of bytes written from p (0 <= n <= len(p))// and any error encountered that caused the write to stop early.// Write must return a non-nil error if it returns n < len(p).// Write must not modify the slice data, even temporarily.//// Implementations must not retain p.type Writer interface {
    Write(p []byte) (n int, err error)}
AI 代码解读


这是Wirter接口的定义,它只有一个Write方法。它接受一个byte的切片,返回两个值,n表示写入的字节数、err表示写入时发生的错误。


从其文档注释来看,这个方法是有规范要求的,我们要想实现一个io.Writer接口,就要遵循这些规则。


  • write方法向底层数据流写入len(p)字节的数据,这些数据来自于切片p;


  • 返回被写入的字节数n,0 <= n <= len(p);


  • 如果n<len(p), 则必须返回一些非nil的err;


  • 如果中途出现问题,也要返回非nil的err;


  • Write方法绝对不能修改切片p以及里面的数据。


这些实现io.Writer接口的规则,所有实现了该接口的类型都要遵守,不然可能会导致莫名其妙的问题。


// Reader is the interface that wraps the basic Read method.//// Read reads up to len(p) bytes into p. It returns the number of bytes// read (0 <= n <= len(p)) and any error encountered. Even if Read// returns n < len(p), it may use all of p as scratch space during the call.// If some data is available but not len(p) bytes, Read conventionally// returns what is available instead of waiting for more.//// When Read encounters an error or end-of-file condition after// successfully reading n > 0 bytes, it returns the number of// bytes read. It may return the (non-nil) error from the same call// or return the error (and n == 0) from a subsequent call.// An instance of this general case is that a Reader returning// a non-zero number of bytes at the end of the input stream may// return either err == EOF or err == nil. The next Read should// return 0, EOF.//// Callers should always process the n > 0 bytes returned before// considering the error err. Doing so correctly handles I/O errors// that happen after reading some bytes and also both of the// allowed EOF behaviors.//// Implementations of Read are discouraged from returning a// zero byte count with a nil error, except when len(p) == 0.// Callers should treat a return of 0 and nil as indicating that// nothing happened; in particular it does not indicate EOF.//// Implementations must not retain p.type Reader interface {
    Read(p []byte) (n int, err error)}
AI 代码解读


这是io.Reader接口定义,也只有一个Read方法。这个方法接受一个byte的切片,并返回两个值,一个是读入的字节数,一个是err错误。


从其注释文档看,io.Reader接口的规则更多。


  • Read最多读取len(p)字节的数据,并保存到p;


  • 返回读取的字节数以及任何发生的错误信息;


  • n要满足0 <= n <= len(p);


  • n<len(p)时,表示读取的数据不足以填满p,这时方法会立即返回,而不是等待更多的数据;


  • 读取过程中遇到错误,会返回读取的字节数n以及相应的错误err;


  • 在底层输入流结束时,方法会返回n>0的字节,但是err可能时EOF,也可以是nil;


  • 在第6种(上面)情况下,再次调用read方法的时候,肯定会返回0,EOF;


  • 调用Read方法时,如果n>0时,优先处理处理读入的数据,然后再处理错误err,EOF也要这样处理;


  • Read方法不鼓励返回n=0并且err=nil的情况。


规则稍微比Write接口有点多,不过也都比较好理解。注意第 8 条,即使我们在读取的时候遇到错误,但是也应该处理已经读到的数据。因为这些已经读到的数据是正确的,如果不进行处理丢失的话,读到的数据就不完整了。


示例


对这两个接口了解后,我们就可以尝试使用他们了,现在来看个例子。


func main() {
    //定义零值Buffer类型变量b
    var b bytes.Buffer
    //使用Write方法为写入字符串
    b.Write([]byte("你好"))    
    //这个是把一个字符串拼接到Buffer里
    fmt.Fprint(&b,",","http://www.flysnow.org")    
    //把Buffer里的内容打印到终端控制台
    b.WriteTo(os.Stdout)}
AI 代码解读


这个例子是拼接字符串到Buffer里,然后再输出到控制台。它非常简单,但是利用了流的读写,bytes.Buffer是一个可变字节的类型,可以让我们很容易的对字节进行操作,比如读写、追加等。bytes.Buffer实现了io.Writerio.Reader接口,所以我们可以很容易地进行读写操作,而不用关注具体实现。


b.Write([]byte("你好"))实现了写入一个字符串。我们把这个字符串转为一个字节切片,然后调用Write方法写入,这个就是bytes.Buffer为了实现io.Writer接口而实现的一个方法,可以帮我们写入数据流。


func (b *Buffer) Write(p []byte) (n int, err error) {
    b.lastRead = opInvalid
    m := b.grow(len(p))
    return copy(b.buf[m:], p), nil}
AI 代码解读


以上就是bytes.Buffer实现io.Writer接口的方法。最终我们看到,写入的切片会被拷贝到b.buf里,这里b.buf[m:]拷贝其实就是追加的意思,不会覆盖已经存在的数据。


从实现看,我们发现其实只有b *Buffer指针实现了io.Writer接口,所以我们示例代码中调用fmt.Fprint函数的时候,传递的是一个地址&b


func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
    p := newPrinter()
    p.doPrint(a)
    n, err = w.Write(p.buf)
    p.free()
    return}
AI 代码解读


这是函数fmt.Fprint的实现,它的功能就是实现把数据a写入到一个io.Writer接口,具体如何写入,它是不关心的。因为这都是io.Writer会做的,它只关心可以写入即可。w.Write(p.buf)调用Wirte方法写入。


最后的b.WriteTo(os.Stdout)是把最终的数据输出到标准的os.Stdout里,以便我们查看输出,它接收一个io.Writer接口类型的参数。开篇我们讲过os.Stdout也实现了这个io.Writer接口,所以就可以作为参数传入。


这里我们会发现,很多方法的接收参数都是io.Writer接口,当然还有io.Reader接口,这就是面向接口的编程。我们不用关注具体实现,只关注这个接口可以做什么事情。如果我们换成输出到文件里,那也很容易,只需把os.File类型作为参数即可。任何实现了该接口的类型,都可以作为参数。


除了b.WriteTo方法外,我们还可以使用io.Reader接口的Read方法实现数据的读取。


var p [100]byten,err:=b.Read(p[:])fmt.Println(n,err,string(p[:n]))
AI 代码解读


这是最原始的方法,使用Read方法,n为读取的字节数,然后我们输出打印出来。


因为byte.Buffer指针实现了io.Reader接口,所以我们还可以使用如下方式读取数据信息。


data,err:=ioutil.ReadAll(&b)fmt.Println(string(data),err)
AI 代码解读


ioutil.ReadAll接收了一个io.Reader接口的参数,表明可以从任何实现了io.Reader接口的类型里读取全部的数据。


func readAll(r io.Reader, capacity int64) (b []byte, err error) {
    buf := bytes.NewBuffer(make([]byte, 0, capacity))
    // If the buffer overflows, we will get bytes.ErrTooLarge.
    // Return that as an error. Any other panic remains.
    defer func() {
        e := recover()        
        if e == nil {
                    return
        }       
        if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
                   err = panicErr            } else {
                    panic(e)
        }
    }()
    _, err = buf.ReadFrom(r)    return buf.Bytes(), err}
AI 代码解读


以上是ioutil.ReadAll实现的源代码,也非常简单。基本原理是创建一个byte.Buffer,通过byte.BufferReadFrom方法,把io.Reader里的数据读取出来,最后通过byte.BufferBytes方法进行返回,最终读取字节数据信息。


整个流的读取和写入已经被完全抽象啦,io包的大部分操作和类型都是基于这两个接口。当然还有http等其他牵涉到数据流、文件流等,都可以完全用io.Writerio.Reader接口来表示,通过这两个接口的连接,我们可以实现任何数据的读写。


目录
打赏
0
0
0
0
9
分享
相关文章
Java IO流实战技巧:如何优化InputStream/OutputStream和Reader/Writer的使用?
【6月更文挑战第26天】Java IO流优化涉及缓冲、资源管理、字符编码和流式处理。使用Buffered流提高读写效率,如`BufferedInputStream`和`BufferedReader`。确保资源关闭使用try-with-resources,如`try (InputStream is = ...) {...}`。处理文本时指定编码,如`InputStreamReader(is, StandardCharsets.UTF_8)`防止乱码。流式处理大文件,分块读写避免内存溢出,以减少内存占用。这些技巧能提升程序性能和健壮性。
299 0
Java IO流专家级教程:深入理解InputStream/OutputStream和Reader/Writer的内部机制
【6月更文挑战第26天】Java IO流涉及字节流(InputStream/OutputStream)和字符流(Reader/Writer),用于高效处理数据输入输出。InputStream/OutputStream处理二进制数据,常使用缓冲提升性能;Reader/Writer处理文本,关注字符编码转换。两者都有阻塞IO操作,但Java NIO支持非阻塞。示例代码展示了如何使用FileInputStream/FileOutputStream和FileReader/FileWriter读写文件。理解这些流的内部机制有助于优化代码性能。
143 0
|
7月前
|
Java IO流终极指南:从InputStream/OutputStream到Reader/Writer的全面解读
【6月更文挑战第26天】Java IO流涵盖字节流(InputStream/OutputStream)和字符流(Reader/Writer),前者处理二进制数据,后者专司文本。例如,FileInputStream/FileOutputStream用于文件的字节级读写,而FileReader/FileWriter处理字符级文本。Buffered流提供缓冲功能,提升效率。选择合适的流类取决于数据类型和性能需求。
132 0
|
7月前
|
深入探索Java IO流:InputStream/OutputStream与Reader/Writer的奥秘!
【6月更文挑战第26天】Java IO流用于输入输出操作,包括字节流(InputStream/OutputStream)和字符流(Reader/Writer)。InputStream和OutputStream处理字节数据,是所有字节流的基类,可被继承以自定义读写行为。
111 0
Java IO流大揭秘:如何高效使用InputStream/OutputStream和Reader/Writer?
【6月更文挑战第26天】Java IO流核心基础,涉及InputStream/OutputStream(字节流)和Reader/Writer(字符流)。高效使用的关键包括:使用Buffered流提升性能,如BufferedInputStream和BufferedOutputStream;处理编码,通过InputStreamReader和OutputStreamWriter指定如UTF-8编码;应用装饰器模式,如DataOutputStream增强功能。理解并巧妙运用这些技巧能优化数据读写操作。
154 0
InputStream、OutputStream、Reader、Writer的基本概念和使用方法
InputStream、OutputStream、Reader、Writer的基本概念和使用方法
152 0
Go语言实现try-catch
在许多编程语言中,try-catch是一种常见的错误处理机制,可以捕获和处理异常。然而,Go语言本身并没有提供类似的try-catch语法。本文将介绍如何在Go语言中实现类似的try-catch机制,以便更好地处理异常情况。
675 0
【Java I/O 流】字符输入输出流:Reader 和 Writer
前面我们已经学会了字节流的使用,本篇开始介绍字符流。字符输入输出流是所有字符流的超类,学会了它们的方法,也基本上掌握了其子类的使用了。
146 0
Go 入门很简单:Writer 和 Reader 接口
我们学习其他语言编程时,会学到一个 io 包,这个包可以以流的方式高效处理数据,而不用考虑数据是什么,数据来自哪里,以及数据要发送到哪里的问题。
JAVA:文本文件读写使用Reader/Writer,二进制文件使用InputStream/OutputStream
JAVA:文本文件读写使用Reader/Writer,二进制文件使用InputStream/OutputStream
93 0
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等