36

如何从io.Reader 中读数据

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzU4ODgyMDI0Mg%3D%3D&%3Bmid=2247487440&%3Bidx=1&%3Bsn=640ff9c4e9dc30f4cdf9082e5ae16a85
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

女主宣言

Go语言 以其本身具有的高并发 特性,在云计算开发中,得到了广泛的应用,也深受广大开发者的欢迎。但是大家对go语言真的理解了么?本文作者经过对go语言的多年实践应用,现对go语言中如何从io.Reader中读数据进行了详细介绍,相信对于go语言爱好者有很大的帮助。下来就跟随作者一起学习下吧。

PS:丰富的一线技术、多元化的表现形式,尽在“ 3 60云计算 ”,点关注哦!

M7baAri.jpg!web

1

概述

开发过程中,我们经常从io.Reader中读取数据。

type Reader interface {

Read(p []byte) (n int, err error)

}

  1. 一次最多读取len(p)长度的数据。

  2. 当读取遭遇到error或EOF, 会返回已读取的数据的字节数和error或EOF。

  3. Read方法,不会修改len(p)的大小。

  4. 使用io.EOF 代表结束了。

Talk is cheap. Show me the code ,下面是一个从read读取的案例:

package main

import (

"fmt"

"io"

"net"

)

func main() {

// 建立tcp连接

conn, err := net.Dial("tcp", "www.findme.wang:80")

if err != nil {

fmt.Println("dial error:", err)

return

}

defer conn.Close() // 关闭连接

// 构建http协议内容,发起http请求

httpReq := `GET / HTTP/1.0

Host: www.findme.wang

User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36

Content-Type:application/x-www-form-urlencoded

Content-Length:0

`

_, err = fmt.Fprintf(conn, httpReq)

if err != nil {

fmt.Println("http request error:", err)

return

}

// read from conn

rsData := make([]byte, 0)

for {

// 每次最多读取512 个字节

var tmp = make([]byte, 512)

n, err := conn.Read(tmp)

if n >= 0 {

rsData = append(rsData, tmp[:n]...)

}

if err == io.EOF {

fmt.Println("数据读取完毕")

break

} else if err != nil {

fmt.Println("读取数据报错:", err)

break

}

}

fmt.Println("读取的数据长度:", len(rsData))

}

在案例中,我们利用for循环反复的读,有没有简洁的方式呢?

2

利用io.copy读取

io.copy定义如下:

func Copy(dst Writer, src Reader) (written int64, err error) {

return copyBuffer(dst, src, nil)

}

将reader中内容读取到dst中的数据,读取到dst中,所以我们需要一个writer 就行,来吧,封装一个如下:

package main

import (

"fmt"

"io"

"net"

)

type MyWriter struct {

data []byte

}

func (m *MyWriter) Write(p []byte) (n int, err error) {

if m.data == nil {

m.data = make([]byte, 0)

}

if p != nil && len(p) != 0 {

m.data = append(m.data, p...)

}

return len(p), nil

}

func main() {

// 建立tcp连接

conn, err := net.Dial("tcp", "www.findme.wang:80")

if err != nil {

fmt.Println("dial error:", err)

return

}

defer conn.Close() // 关闭连接

// 构建http协议内容,发起http请求

httpReq := `GET / HTTP/1.0

Host: www.findme.wang

User- Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36

Content-Type:application/x-www-form-urlencoded

Content-Length:0

`

_, err = fmt.Fprintf(conn, httpReq)

if err != nil {

fmt.Println("http request error:", err)

return

}

w := new(MyWriter)

n, err := io.Copy(w, conn) // 将 conn中的数据读取到 writer中

if err != nil {

fmt.Println("读取err ", err)

}

//fmt.Println(string(w.data))// 打印数据

fmt.Println("读取的数据长度:", n)

}

从io读取数据虽然是简单了,但是需要封装一个writer。那么,go里面是否有类似的writer呢?能够让我们很容易获取数据的writer呢?

于是,我们找到了strings.buffer ,如下:

// A Builder is used to efficiently build a string using Write methods.

// It minimizes memory copying. The zero value is ready to use.

// Do not copy a non-zero Builder.

type Builder struct {

addr *Builder // of receiver, to detect copies by value

buf  []byte

}

有了strings.buffer,代码又可精简一波。

package main

import (

"fmt"

"io"

"net"

"strings"

)

func main() {

// 建立tcp连接

conn, err := net.Dial("tcp", "www.findme.wang:80")

if err != nil {

fmt.Println("dial error:", err)

return

}

defer conn.Close() // 关闭连接

// 构建http协议内容,发起http请求

httpReq := `GET / HTTP/1.0

Host: www.findme.wang

User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36

Content-Type:application/x-www-form-urlencoded

Content-Length:0

`

_, err = fmt.Fprintf(conn, httpReq)

if err != nil {

fmt.Println("http request error:", err)

return

}

var sb strings.Builder

n, err := io.Copy(&sb, conn) // 将 conn中的数据读取到 writer中

if err != nil {

fmt.Println("读取err ", err)

}

fmt.Println(sb.String()) // print res

fmt.Println("读取的数据长度:", n)

}

除了,使用strings.buffer,我们还可以使用bytes.Buffer。

3

使用ioutil.ReadAll

ReadAll(r io.Reader) ([]byte, error) 是一次性从输入流(reader)中读取全量数据,直到发送错误或EOF。若读取失败,返回已读数据和err;若读取成功,则返回全量数据和nil。即改方法,不会返回EOF,

案例如下:

package main

import (

"fmt"

"io/ioutil"

"net"

)

func main() {

// 建立tcp连接

conn, err := net.Dial("tcp", "www.findme.wang:80")

if err != nil {

fmt.Println("dial error:", err)

return

}

defer conn.Close() // 关闭连接

// 构建http协议内容,发起http请求

httpReq := `GET / HTTP/1.0

Host: www.findme.wang

User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36

Content-Type:application/x-www-form-urlencoded

Content-Length:0

`

_, err = fmt.Fprintf(conn, httpReq)

if err != nil {

fmt.Println("http request error:", err)

return

}

data, err := ioutil.ReadAll(conn)

if err != nil {

fmt.Println("读取err ", err)

}

fmt.Println(string(data)) // print res

fmt.Println("读取的数据长度:", len(data))

}

4

补充

此外,我们还可以使用io包提供的一些方法,比如:io.ReadAtLeast、io.ReadFull等.

1、io.ReadAtLeast

从输入流中至少min个字节,放到buf中,返回读取的字节数和err,结构如下:

func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) {

if len(buf) < min {

return 0, ErrShortBuffer

}

for n < min && err == nil {

var nn int

nn, err = r.Read(buf[n:])

n += nn

}

if n >= min { //读取字节不小于min的时候,把err 设置nil

err = nil

} else if n > 0 && err == EOF {

err = ErrUnexpectedEOF

}

return

}

  • 如果buf的长度小于 min,会触发ErrShortBuffer 。

  • 如果读取的字节数小于min,这会触发ErrUnexpectedEOF 错误。

  • 如果读取的字节数不小于min ,就算遇到了err,也会返回nil。

2、io.ReadFull 

func ReadFull(r Reader, buf []byte) (n int, err error) {

return ReadAtLeast(r, buf, len(buf))

}

io.ReadFull本质上面调用了io.ReadAtLeast,在此不再赘述。

360云计算

由360云平台团队打造的技术分享公众号,内容涉及 数据库、大数据、微服务、容器、AIOps、IoT 等众多技术领域,通过夯实的技术积累和丰富的一线实战经验,为你带来最有料的技术分享

ym2U3qr.png!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK