Golang IO包的妙用
那个小码哥 · · 2001 次点击 · · 开始浏览作者 丨icexin
Golang 标准库对 IO 的抽象非常精巧,各个组件可以随意组合,可以作为接口设计的典范。这篇文章结合一个实际的例子来和大家分享一下。
背景
以一个RPC的协议包来说,每个包有如下结构:
type Packet struct {
TotalSize uint32
Magic [4]byte
Payload []byte
Checksum uint32
}
其中 TotalSize 是整个包除去 TotalSize 后的字节数, Magic 是一个固定长度的字串,Payload 是包的实际内容,包含业务逻辑的数据。Checksum 是对 Magic 和 Payload 的adler32 校验和。
编码(encode)
我们使用一个原型为func EncodePacket(w io.Writer, payload []byte) error的函数来把数据打包,结合encoding/binary我们很容易写出第一版,演示需要,错误处理方面就简化处理了:
var RPC_MAGIC = [4]byte{'p', 'y', 'x', 'i'}
func EncodePacket(w io.Writer, payload []byte) error {
// len(Magic) + len(Checksum) == 8
totalsize := uint32(len(payload) + 8)
// write total size
binary.Write(w, binary.BigEndian, totalsize)
// write magic bytes
binary.Write(w, binary.BigEndian, RPC_MAGIC)
// write payload
w.Write(payload)
// calculate checksum
var buf bytes.Buffer
buf.Write(RPC_MAGIC[:])
buf.Write(payload)
checksum := adler32.Checksum(buf.Bytes())
// write checksum
return binary.Write(w, binary.BigEndian, checksum)
}
在上面的实现中,为了计算 checksum,我们使用了一个内存 buffer 来缓存数据,最后把所有的数据一次性读出来算 checksum,考虑到计算 checksum 是一个不断 update 地过程,我们应该有方法直接略过内存 buffer 而计算 checksum。
查看 hash/adler32 我们得知,我们可以构造一个 Hash32 的对象,这个对象内嵌了一个 Hash 的接口,这个接口的定义如下:
type Hash interface {
// Write (via the embedded io.Writer interface) adds more data to the running hash.
// It never returns an error.
io.Writer
// Sum appends the current hash to b and returns the resulting slice.
// It does not change the underlying hash state.
Sum(b []byte) []byte
// Reset resets the Hash to its initial state.
Reset()
// Size returns the number of bytes Sum will return.
Size() int
// BlockSize returns the hash's underlying block size.
// The Write method must be able to accept any amount
// of data, but it may operate more efficiently if all writes
// are a multiple of the block size.
BlockSize() int
}
这是一个通用的计算 hash 的接口,标准库里面所有计算 hash 的对象都实现了这个接口,比如md5, crc32等。由于 Hash 实现了 io.Writer 接口,因此我们可以把所有要计算的数据像写入文件一样写入到这个对象中,最后调用 Sum(nil) 就可以得到最终的 hash 的 byte 数组。利用这个思路,第二版可以这样写:
func EncodePacket2(w io.Writer, payload []byte) error {
// len(Magic) + len(Checksum) == 8
totalsize := uint32(len(RPC_MAGIC) + len(payload) + 4)
// write total size
binary.Write(w, binary.BigEndian, totalsize)
// write magic bytes
binary.Write(w, binary.BigEndian, RPC_MAGIC)
// write payload
w.Write(payload)
// calculate checksum
sum := adler32.New()
sum.Write(RPC_MAGIC[:])
sum.Write(payload)
checksum := sum.Sum32()
// write checksum
return binary.Write(w, binary.BigEndian, checksum)
}
注意这次的变化,前面写入 TotalSize,Magic,Payload 部分没有变化,在计算 checksum 的时候去掉了 bytes.Buffer,减少了一次内存申请和拷贝。
考虑到 sum 和 w 都是 io.Writer,利用神奇的 crypto/aes 来个 AES 加密...
在这个时候,文件已经不再局限于 io,可以是一个内存 buffer,也可以是一个计算 hash 的对象,甚至是一个计数器,流量限速器。Golang 灵活的接口机制为我们提供了无限可能。
结尾
我一直认为一个好的语言一定有一个设计良好的标准库,Golang 的标准库是作者们多年系统编程的沉淀,值得我们细细品味。
技术交流群:426582602,本文仅授权51 Reboot相关账号发布。
有疑问加站长微信联系(非本文作者)
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
关注微信- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码` - 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传
收入到我管理的专栏 新建专栏
作者 丨icexin
Golang 标准库对 IO 的抽象非常精巧,各个组件可以随意组合,可以作为接口设计的典范。这篇文章结合一个实际的例子来和大家分享一下。
背景
以一个RPC的协议包来说,每个包有如下结构:
type Packet struct {
TotalSize uint32
Magic [4]byte
Payload []byte
Checksum uint32
}
其中 TotalSize 是整个包除去 TotalSize 后的字节数, Magic 是一个固定长度的字串,Payload 是包的实际内容,包含业务逻辑的数据。Checksum 是对 Magic 和 Payload 的adler32 校验和。
编码(encode)
我们使用一个原型为func EncodePacket(w io.Writer, payload []byte) error的函数来把数据打包,结合encoding/binary我们很容易写出第一版,演示需要,错误处理方面就简化处理了:
var RPC_MAGIC = [4]byte{'p', 'y', 'x', 'i'}
func EncodePacket(w io.Writer, payload []byte) error {
// len(Magic) + len(Checksum) == 8
totalsize := uint32(len(payload) + 8)
// write total size
binary.Write(w, binary.BigEndian, totalsize)
// write magic bytes
binary.Write(w, binary.BigEndian, RPC_MAGIC)
// write payload
w.Write(payload)
// calculate checksum
var buf bytes.Buffer
buf.Write(RPC_MAGIC[:])
buf.Write(payload)
checksum := adler32.Checksum(buf.Bytes())
// write checksum
return binary.Write(w, binary.BigEndian, checksum)
}
在上面的实现中,为了计算 checksum,我们使用了一个内存 buffer 来缓存数据,最后把所有的数据一次性读出来算 checksum,考虑到计算 checksum 是一个不断 update 地过程,我们应该有方法直接略过内存 buffer 而计算 checksum。
查看 hash/adler32 我们得知,我们可以构造一个 Hash32 的对象,这个对象内嵌了一个 Hash 的接口,这个接口的定义如下:
type Hash interface {
// Write (via the embedded io.Writer interface) adds more data to the running hash.
// It never returns an error.
io.Writer
// Sum appends the current hash to b and returns the resulting slice.
// It does not change the underlying hash state.
Sum(b []byte) []byte
// Reset resets the Hash to its initial state.
Reset()
// Size returns the number of bytes Sum will return.
Size() int
// BlockSize returns the hash's underlying block size.
// The Write method must be able to accept any amount
// of data, but it may operate more efficiently if all writes
// are a multiple of the block size.
BlockSize() int
}
这是一个通用的计算 hash 的接口,标准库里面所有计算 hash 的对象都实现了这个接口,比如md5, crc32等。由于 Hash 实现了 io.Writer 接口,因此我们可以把所有要计算的数据像写入文件一样写入到这个对象中,最后调用 Sum(nil) 就可以得到最终的 hash 的 byte 数组。利用这个思路,第二版可以这样写:
func EncodePacket2(w io.Writer, payload []byte) error {
// len(Magic) + len(Checksum) == 8
totalsize := uint32(len(RPC_MAGIC) + len(payload) + 4)
// write total size
binary.Write(w, binary.BigEndian, totalsize)
// write magic bytes
binary.Write(w, binary.BigEndian, RPC_MAGIC)
// write payload
w.Write(payload)
// calculate checksum
sum := adler32.New()
sum.Write(RPC_MAGIC[:])
sum.Write(payload)
checksum := sum.Sum32()
// write checksum
return binary.Write(w, binary.BigEndian, checksum)
}
注意这次的变化,前面写入 TotalSize,Magic,Payload 部分没有变化,在计算 checksum 的时候去掉了 bytes.Buffer,减少了一次内存申请和拷贝。
考虑到 sum 和 w 都是 io.Writer,利用神奇的 crypto/aes 来个 AES 加密...
在这个时候,文件已经不再局限于 io,可以是一个内存 buffer,也可以是一个计算 hash 的对象,甚至是一个计数器,流量限速器。Golang 灵活的接口机制为我们提供了无限可能。
结尾
我一直认为一个好的语言一定有一个设计良好的标准库,Golang 的标准库是作者们多年系统编程的沉淀,值得我们细细品味。
技术交流群:426582602,本文仅授权51 Reboot相关账号发布。