golang中的io.Reader/Writer
Posted SuPhoebe
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了golang中的io.Reader/Writer相关的知识,希望对你有一定的参考价值。
Go原生的包中有一些核心的interface
,其中io.Reader/Writer
是比较常用的接口。很多原生的结构都围绕这个系列的接口展开,在实际的开发过程中,你会发现通过这个接口可以在多种不同的io类型之间进行过渡和转化。本文结合实际场景来总结一番。
关系图表
type Reader interface
Read(p []byte) (n int, err error)
type Writer interface
Write(p []byte) (n int, err error)
可见,任何实现了 Read()
函数的对象都可以作为 Reader
来使用。Write()
也是同理。
围绕io.Reader/Writer
,有几个常用的实现:
net.Conn
,os.Stdin
,os.File
: 网络、标准输入输出、文件的流读取strings.Reader
: 把字符串抽象成Readerbytes.Reader
: 把[]byte抽象成Readerbytes.Buffer
: 把[]byte抽象成Reader和Writerbufio.Reader/Writer
: 抽象成带缓冲的流读取(比如按行读写)
场景举例
0. 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())
1. []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
1的包可以实现带缓冲的读写:
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'))
//...
花式技(zuo)巧(si)
string转[]byte
a := "Hello, playground"
fmt.Println([]byte(a))
等价于
a := "Hello, playground"
buf := new(bytes.Buffer)
buf.ReadFrom(strings.NewReader(a))
fmt.Println(buf.Bytes())
参考资料
以上是关于golang中的io.Reader/Writer的主要内容,如果未能解决你的问题,请参考以下文章