golang io read writer使用

Writer和Reader是两个抽象的接口,其定义如下

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

type Reader interface {
    Read(p []byte) (n int, err error)
}
查看代码
// 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)
}

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

// Closer is the interface that wraps the basic Close method.
//
// The behavior of Close after the first call is undefined.
// Specific implementations may document their own behavior.
type Closer interface {
	Close() error
}

 

很多方法的接收参数都是io.Writer接口以及io.Reader接口,很多原生的结构都围绕这个系列的接口展开,在实际的开发过程中,通过这个接口可以在多种不同的io类型之间进行过渡和转化。

  • net.Conn, os.Stdin, os.File: 网络、标准输入输出、文件的流读取
  • strings.Reader: 把字符串抽象成Reader
  • bytes.Reader: 把[]byte抽象成Reader
  • bytes.Buffer: 把[]byte抽象成Reader和Writer
  • bufio.Reader/Writer: 抽象成带缓冲的流读取(比如按行读写)

 

reader

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的情况

io.Reader 表示一个读取器,它将数据从某个资源读取到传输缓冲区。在缓冲区中,数据可以被流式传输和使用。

func main() {
    reader := strings.NewReader("Clear is better than clever")
    p := make([]byte, 4)

    for {
        n, err := reader.Read(p)
        if err != nil{
            if err == io.EOF {
                fmt.Println("EOF:", n)
                break
            }
            fmt.Println(err)
            os.Exit(1)
        }
        fmt.Println(n, string(p[:n]))
    }
}

$ go run test.go
4 Clea
4 r is
4  bet
4 ter 
4 than
4  cle
3 ver
EOF: 0

writer

io.Writer 表示一个编写器,它从缓冲区读取数据,并将数据写入目标资源。

要想实现一个io.Writer接口,就要遵循这些规则。

  • write方法向底层数据流写入len(p)字节的数据,这些数据来自于切片p
  • 返回被写入的字节数n,0 <= n <= len(p)
  • 如果n<len(p), 则必须返回一些非nil的err
  • 如果中途出现问题,也要返回非nil的err
  • Write方法绝对不能修改切片p以及里面的数据

下面是一个简单的例子,它使用 bytes.Buffer 类型作为 io.Writer 将数据写入内存缓冲区。

func main() {
    proverbs := []string{
        "Channels orchestrate mutexes serialize",
        "Cgo is not Go",
        "Errors are values",
        "Don't panic",
    }
    var writer bytes.Buffer

    for _, p := range proverbs {
        n, err := writer.Write([]byte(p))
        if err != nil {
            fmt.Println(err)
            os.Exit(1)
        }
        if n != len(p) {
            fmt.Println("failed to write data")
            os.Exit(1)
        }
    }

    fmt.Println(writer.String())
}

 

为什么需要ReadCloser这种变体?比如http.Response.Body

如果没有关闭resp.Body,golang将继续保持连接以重用它,

/*If the returned error is nil, the Response will contain a non-nil Body which the user is expected to close.
If the Body is not both read to EOF and closed, the Client's underlying RoundTripper (typically Transport) 
may not be able to re-use a persistent TCP connection to the server for a subsequent "keep-alive" request.*/

 

io.reader复制

io.reader 被读取一次后就没了,但有些情况下我们需要复制。

最麻烦的方式,将数据读出来,再用读出来的数据,创建两个Reader。

你可以先把 io.Reader 类型的对象读到 []bytes 类型的对象里面,通过 []bytes 类型对象创建多个 io.Reader 比如:

b, err :=  ioutil.ReadAll(io.Reader)
reader1 := bytes.NewReader(b)
reader2 := bytes.NewReader(b)

 

io.Copy

func Copy(dst Writer, src Reader) (written int64, err error) {
    return copyBuffer(dst, src, nil)
}

io.Copy() 可以轻松地将数据从一个 Reader 拷贝到另一个 Writer。
它抽象出 for 循环模式(我们上面已经实现了)并正确处理 io.EOF 和 字节计数。

io.Copy

func Copy(dst Writer, src Reader) (written int64, err error)可以从src复制数据到dst(Go 实现的方法一般把目的地址参数放在源参数的前面),直到:

  • EOF
  • 其它error

它返回读取的字节数以及遇到的第一个错误

注意成功的读取完输入流后err并不是io.EOF, 而是nil
它内部是调用CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error)实现的,
CopyBuffer会有可能使用一个指定的buf来复制数据,如果buf的长度为0, 它会panic。

 

bufio

bufio 包实现了缓存IO。它包装了 io.Reader 和 io.Writer 对象,创建了另外的Reader和Writer对象,它们也实现了 io.Reader 和 io.Writer 接口,不过它们是有缓存的。该包同时为文本I/O提供了一些便利操作。

type Reader struct {
    buf          []byte        // 缓存
    rd           io.Reader    // 底层的io.Reader
    // r:从buf中读走的字节(偏移);w:buf中填充内容的偏移;
    // w - r 是buf中可被读的长度(缓存数据的大小),也是Buffered()方法的返回值
    r, w         int
    err          error        // 读过程中遇到的错误
    lastByte     int        // 最后一次读到的字节(ReadByte/UnreadByte)
    lastRuneSize int        // 最后一次读到的Rune的大小 (ReadRune/UnreadRune)
}

type Writer struct {
    err error        // 写过程中遇到的错误
    buf []byte        // 缓存
    n   int            // 当前缓存中的字节数
    wr  io.Writer    // 底层的 io.Writer 对象
}
 

bufio.Reader

  • ReadSlice (返回的是指针,不是一个拷贝)
  • ReadBytes
  • ReadString
  • ReadLine

一般情况下用ReadBytes、ReadString就好

package main

import (
    "bufio"
    "fmt"
    "strings"
)

func main() {
    reader := bufio.NewReader(strings.NewReader("http://studygolang.com. \nIt is the home of gophers"))
    line, _ := reader.ReadBytes('\n')
    fmt.Printf("the line:%s\n", line)
    // 这里可以换上任意的 bufio 的 Read/Write 操作
    n, _ := reader.ReadBytes('\n')
    fmt.Printf("the line:%s\n", line)
    fmt.Println(string(n))
}

 

一个Reader多次读取

io.TeeReader

读取的时候,同时输出到os.Stdout中

package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "os"
    "strings"
)

func main() {
    var r io.Reader = strings.NewReader("some io.Reader stream to be read\n")

    r = io.TeeReader(r, os.Stdout)

    // Everything read from r will be copied to stdout.
    b, _ := ioutil.ReadAll(r)
    fmt.Println(string(b))
}
复制

一个Writer多次写入

io.MultiWriter

func test() {
    file, err := os.Create("tmp.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    writer := io.MultiWriter(file, os.Stdout)
    writer.Write([]byte("Hello"))
}
 

并发对一个steam进行读写

可以参考: https://github.com/djherbis/stream

 

ioutil.ReadAll

ReadAll(r io.Reader) ([]byte, error)提供了一个从输入流中读取全部数据的方法,它读取输入流直到出现错误(error)或者读到头(EOF)。

成功的读取到头不会返回io.EOF, 而是返回数据和nil。

我们经常用它来读取可信的http.Response.Body。

 

io.ReadFull

ReadFull(r Reader, buf []byte) (n int, err error)从输入流中读取正好len(buf)字节的数据。

  • 读取到0字节,并且遇到EOF, 返回EOF
  • 读取到0<n<len(buf)字节,并且遇到EOF, 返回ErrUnexpectedEOF
  • 读取到n==len(buf),即使遇到error也返回err=nil
  • 返回err != nil则肯定n<len(buf)

io.ReadAtLeast

ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)从输入流中读取至少min个字节到buf中,直到把buf读取满或者遇到 error,包括EOF。

  • 如果n < min, 肯定返回错误
  • 没有数据可读返回n=0和EOF
  • n < min,并且遇到EOF,返回n和ErrUnexpectedEOF
  • 如果参数min>len(buf),返回0,ErrShortBuffer
  • 如果读取了至少min个字节,即使遇到错误也会返回err=nil

 

io.LimitReader

LimitReader(r Reader, n int64) Reader返回一个内容长度受限的Reader,当从这个Reader中读取了n个字节一会就会遇到EOF。

设想一下如果没有这个保护措施,别让告诉你发送一个图片,结果发送给你一个3G的葫芦娃的视频,有可能会使你的内存飙升。

它就是提供了一个保护的功能,其它和普通的Reader没有两样。

 

参考:io包解析

 

base64编码成字符串

encoding/base64包中:

func NewEncoder(enc *Encoding, w io.Writer) io.WriteCloser

这个用来做base64编码,但是仔细观察发现,它需要一个io.Writer作为输出目标,并用返回的WriteCloser的Write方法将结果写入目标,下面是Go官方文档的例子

input := []byte("foo\x00bar") 
encoder := base64.NewEncoder(base64.StdEncoding, os.Stdout) 
encoder.Write(input)

这个例子是将结果写入到Stdout,如果我们希望得到一个字符串呢?观察上面的图,不然发现可以用bytes.Buffer作为目标io.Writer

input := []byte("foo\x00bar")
buffer := new(bytes.Buffer) 
encoder := base64.NewEncoder(base64.StdEncoding, buffer) 
encoder.Write(input) 
fmt.Println(string(buffer.Bytes())

 

[]byte和struct之间正反序列化

这种场景经常用在基于字节的协议上,比如有一个具有固定长度的结构:

type Protocol struct { 
Version uint8 
BodyLen uint16 
Reserved [2]byte 
Unit uint8 
Value uint32 
}

通过一个[]byte来反序列化得到这个Protocol,一种思路是遍历这个[]byte,然后逐一赋值。其实在encoding/binary包中有个方便的方法:

func Read(r io.Reader, order ByteOrder, data interface{}) error

这个方法从一个io.Reader中读取字节,并已order指定的端模式,来给填充data(data需要是fixed-sized的结构或者类型)。要用到这个方法首先要有一个io.Reader,从上面的图中不难发现,我们可以这么写:

var p Protocol 
var bin []byte 
//... 
binary.Read(bytes.NewReader(bin), binary.LittleEndian, &p)

换句话说,我们将一个[]byte转成了一个io.Reader

反过来,我们需要将Protocol序列化得到[]byte,使用encoding/binary包中有个对应的Write方法:

func Write(w io.Writer, order ByteOrder, data interface{}) error

通过将[]byte转成一个io.Writer即可:

var p Protocol 
buffer := new(bytes.Buffer) 
//... 
binary.Writer(buffer, binary.LittleEndian, p) 
bin := buffer.Bytes()

2. 从流中按行读取

比如对于常见的基于文本行的HTTP协议的读取,我们需要将一个流按照行来读取。本质上,我们需要一个基于缓冲的读写机制(读一些到缓冲,然后遍历缓冲中我们关心的字节或字符)。在Go中有一个bufio的包可以实现带缓冲的读写:

func NewReader(rd io.Reader) *Reader 
func (b *Reader) ReadString(delim byte) (string, error)

这个ReadString方法从io.Reader中读取字符串,直到delim,就返回delim和之前的字符串。如果将delim设置为\n,相当于按行来读取了:

var conn net.Conn 
//... 
reader := NewReader(conn) 
for { 
    line, err := reader.ReadString([]byte('\n'))
    //... 
}

 

posted @   codestacklinuxer  阅读(314)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)
历史上的今天:
2020-01-05 nginx&http 第三章 ngx 事件http 初始化1
2020-01-05 nginx&http 第三章 ngx 事件event accept epoll /init
2020-01-05 nginx&http 第三章 ngx 事件event epoll 处理
2020-01-05 nginx&http 第二章 ngx 事件event初始化 ngx_event_process_init
点击右上角即可分享
微信分享提示