如何在 Go 中将 []byte 转换为 io.Reader?

简介: 如何在 Go 中将 []byte 转换为 io.Reader?

原文链接:如何在 Go 中将 []byte 转换为 io.Reader?


001-byte-slice-to-io-reader.png


在 stackoverflow 上看到一个问题,题主进行了一个网络请求,接口返回的是 []byte

如果想要将其转换成 io.Reader,需要怎么做呢?


这个问题解决起来并不复杂,简单几行代码就可以轻松将其转换成功。不仅如此,还可以再通过几行代码反向转换回来。


下面听我慢慢给你吹,首先直接看两段代码。


[]byte 转 io.Reader


package main
import (
  "bytes"
  "fmt"
  "log"
)
func main() {
  data := []byte("Hello AlwaysBeta")
  // byte slice to bytes.Reader, which implements the io.Reader interface
  reader := bytes.NewReader(data)
  // read the data from reader
  buf := make([]byte, len(data))
  if _, err := reader.Read(buf); err != nil {
    log.Fatal(err)
  }
  fmt.Println(string(buf))
}
复制代码


输出:


Hello AlwaysBeta
复制代码


这段代码先将 []byte 数据转换到 reader 中,然后再从 reader 中读取数据,并打印输出。


io.Reader 转 []byte


package main
import (
  "bytes"
  "fmt"
  "strings"
)
func main() {
  ioReaderData := strings.NewReader("Hello AlwaysBeta")
  // creates a bytes.Buffer and read from io.Reader
  buf := &bytes.Buffer{}
  buf.ReadFrom(ioReaderData)
  // retrieve a byte slice from bytes.Buffer
  data := buf.Bytes()
  // only read the left bytes from 6
  fmt.Println(string(data[6:]))
}
复制代码


输出:


AlwaysBeta
复制代码


这段代码先创建了一个 reader,然后读取数据到 buf,最后打印输出。


以上两段代码就是 []byteio.Reader 互相转换的过程。对比这两段代码不难发现,都有 NewReader 的身影。而且在转换过程中,都起到了关键作用。


那么问题来了,这个 NewReader 到底是什么呢?接下来我们通过源码来一探究竟。


源码解析


Go 的 io 包提供了最基本的 IO 接口,其中 io.Readerio.Writer 两个接口最为关键,很多原生结构都是围绕这两个接口展开的。


66c88f2dddd343989b2975bf7a35dc57~tplv-k3u1fbpfcp-zoom-in-crop-mark 1304 0 0 0.png


下面就来分别说说这两个接口:


Reader 接口


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


194625b2b16d44398aa688929279afa2~tplv-k3u1fbpfcp-zoom-in-crop-mark 1304 0 0 0.png


接口定义如下:


type Reader interface {
    Read(p []byte) (n int, err error)
}
复制代码


Read() 方法将 len(p) 个字节读取到 p 中。它返回读取的字节数 n,以及发生错误时的错误信息。


举一个例子:


package main
import (
  "fmt"
  "io"
  "os"
  "strings"
)
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]))
  }
}
复制代码


输出:


4 Clea
4 r is
4  bet
4 ter
4 than
4  cle
3 ver
EOF: 0
复制代码


这段代码从 reader 不断读取数据,每次读 4 个字节,然后打印输出,直到结尾。

最后一次返回的 n 值有可能小于缓冲区大小。


Writer 接口


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


194625b2b16d44398aa688929279afa2~tplv-k3u1fbpfcp-zoom-in-crop-mark 1304 0 0 0.png


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


Write 方法将 len(p) 个字节从 p 中写入到对象数据流中。它返回从 p 中被写入的字节数 n,以及发生错误时返回的错误信息。


举一个例子:


package main
import (
  "bytes"
  "fmt"
  "os"
)
func main() {
  // 创建 Buffer 暂存空间,并将一个字符串写入 Buffer
  // 使用 io.Writer 的 Write 方法写入
  var buf bytes.Buffer
  buf.Write([]byte("hello world , "))
  // 用 Fprintf 将一个字符串拼接到 Buffer 里
  fmt.Fprintf(&buf, " welcome to golang !")
  // 将 Buffer 的内容输出到标准输出设备
  buf.WriteTo(os.Stdout)
}
复制代码


输出:


hello world ,  welcome to golang !
复制代码


bytes.Buffer 是一个结构体类型,用来暂存写入的数据,其实现了 io.Writer 接口的 Write 方法。


WriteTo 方法定义:


func (b *Buffer) WriteTo(w io.Writer) (n int64, err error)
复制代码


WriteTo 方法第一个参数是 io.Writer 接口类型。


转换原理


再说回文章开头的转换问题。


只要某个实例实现了接口 io.Reader 里的方法 Read() ,就满足了接口 io.Reader


ed23db796e5445f0a560a088ce228e8e~tplv-k3u1fbpfcp-zoom-in-crop-mark 1304 0 0 0.png


bytesstrings 包都实现了 Read() 方法。


// src/bytes/reader.go
// NewReader returns a new Reader reading from b.
func NewReader(b []byte) *Reader { return &Reader{b, 0, -1} }
复制代码


// src/strings/reader.go
// NewReader returns a new Reader reading from s.
// It is similar to bytes.NewBufferString but more efficient and read-only.
func NewReader(s string) *Reader { return &Reader{s, 0, -1} }
复制代码


在调用 NewReader 的时候,会返回了对应的 T.Reader 类型,而它们都是通过 io.Reader 扩展而来的,所以也就实现了转换。


总结


在开发过程中,避免不了要进行一些 IO 操作,包括打印输出,文件读写,网络连接等。

在 Go 语言中,也提供了一系列标准库来应对这些操作,主要封装在以下几个包中:


  • io:基本的 IO 操作接口。
  • io/ioutil:封装了一些实用的 IO 函数。
  • fmt:实现了 IO 格式化操作。
  • bufio:实现了带缓冲的 IO。
  • net.Conn:网络读写。
  • os.Stdinos.Stdout:系统标准输入输出。
  • os.File:系统文件操作。
  • bytes:字节相关 IO 操作。


除了 io.Readerio.Writer 之外,io 包还封装了很多其他基本接口,比如 ReaderAtWriterAtReaderFromWriterTo 等,这里就不一一介绍了。这部分代码并不复杂,读起来很轻松,而且还能加深对接口的理解,推荐大家看看。


/

目录
相关文章
|
缓存 Go 数据格式
千万不要在Go IO上踩这些雷!
千万不要在Go IO上踩这些雷!
85 0
|
1月前
|
缓存 监控 前端开发
Go 语言中如何集成 WebSocket 与 Socket.IO,实现高效、灵活的实时通信
本文探讨了在 Go 语言中如何集成 WebSocket 与 Socket.IO,实现高效、灵活的实时通信。首先介绍了 WebSocket 和 Socket.IO 的基本概念及其优势,接着详细讲解了 Go 语言中 WebSocket 的实现方法,以及二者集成的重要意义和具体步骤。文章还讨论了集成过程中需要注意的问题,如协议兼容性、消息格式、并发处理等,并提供了实时聊天、数据监控和在线协作工具等应用案例,最后提出了性能优化策略,包括数据压缩、缓存策略和连接管理优化。旨在帮助开发者更好地理解并应用这些技术。
45 3
|
6月前
|
存储 Go API
Go 语言基础之常用包【flag、time、strconv、io】(2)
Go 语言基础之常用包【flag、time、strconv、io】
|
6月前
|
存储 Unix Go
Go 语言基础之常用包【flag、time、strconv、io】(1)
Go 语言基础之常用包【flag、time、strconv、io】
|
7月前
|
安全 Go
Golang深入浅出之-Go语言标准库中的文件读写:io/ioutil包
【4月更文挑战第27天】Go语言的`io/ioutil`包提供简单文件读写,适合小文件操作。本文聚焦`ReadFile`和`WriteFile`函数,讨论错误处理、文件权限、大文件处理和编码问题。避免错误的关键在于检查错误、设置合适权限、采用流式读写及处理编码。遵循这些最佳实践能提升代码稳定性。
80 0
|
7月前
|
缓存 监控 前端开发
【Go 语言专栏】Go 语言中的 WebSocket 与 Socket.IO 集成
【4月更文挑战第30天】本文介绍了在 Go 语言中集成 WebSocket 与 Socket.IO 的相关技术,WebSocket 是一种高效的双向通信协议,Socket.IO 是一个实时通信库,提供丰富的事件处理。集成两者能实现更强大的实时通信功能。文章讨论了 Go 中 WebSocket 的实现,Socket.IO 与 WebSocket 的关系,集成的意义及步骤,并提醒注意协议兼容性、消息格式等问题。此外,还提到了性能优化策略和应用案例,如实时聊天、数据监控和在线协作工具。通过集成,开发者可以构建出满足多样化需求的实时通信应用。
319 0
|
Go
深入解密Go语言的byte和rune用法 文本处理从这里开始
深入解密Go语言的byte和rune用法 文本处理从这里开始
105 0
深入解密Go语言的byte和rune用法 文本处理从这里开始
|
Linux Go 调度
Go学习笔记-协程和IO多路复用
Go学习笔记-协程和IO多路复用
183 0
Go学习笔记-协程和IO多路复用
|
安全 Go
go标准库io|Go主题月
io包提供了系统io最基本的封装,可以用来进行文件的读取,写入,复制等功能。不是线程安全的。
190 0
Go---Go语言io流小项目“地摊管理系统”
Go---Go语言io流小项目“地摊管理系统”