如何从同一io.Reader中多次读取数据

81

我想使用包含图像的request.Body(type io.ReadCloser),但不想使用ioutil.ReadAll(),因为我想直接将该体写入文件并解码它,因此我只想使用对内容的引用来传递给进一步的函数调用。

我尝试创建多个读取器实例,例如下面所示。

package main

import (
    "io/ioutil"
    "log"
    "strings"
)


func main() {
    r := strings.NewReader("some io.Reader stream to be read\n")
    a := &r
    b := &r
    log.Println(ioutil.ReadAll(*a))
    log.Println(ioutil.ReadAll(*b))

}
但在第二次调用中,它总是返回nil。 请帮我,如何为同一读取器传递多个独立的引用?

5
按你的要求无法实现。使用 io.TeeReader 来捕获所读内容以供进一步参考,例如在 bytes.Buffer 中。 - Volker
5个回答

139

io.Reader 被视为流。因此,您不能对其进行两次读取。想象一下传入的 TCP 连接——您无法回放正在到来的内容。

但是,您可以使用 io.TeeReader 复制该流:

package main

import (
    "bytes"
    "io"
    "io/ioutil"
    "log"
    "strings"
)

func main() {
    r := strings.NewReader("some io.Reader stream to be read\n")
    var buf bytes.Buffer
    tee := io.TeeReader(r, &buf)

    log.Println(ioutil.ReadAll(tee))
    log.Println(ioutil.ReadAll(&buf)) 
}

Go Playground上的示例。

编辑:正如@mrclx所指出的那样:您需要先从TeeReader中读取,否则缓冲区将为空。


3
此外,如果流的数据很大 - 比如 1GB - 不要使用 ReadAll。相反 - 由于 OP 希望首先将其写入文件 - 使用 err, _ := io.Copy(fh, tee) - colm.anseo

12
当您从ioutil.ReadAll(r)读取时,内容就已经消失了。您无法第二次从中读取。 例如:
var response *http.Response

//Read the content
rawBody, err := ioutil.ReadAll(response.Body)
    if err != nil {
        t.Error(err)
    }

// Restore the io.ReadCloser to it's original state
response.Body = ioutil.NopCloser(bytes.NewBuffer(rawBody))

8
当你调用 ReadAll 函数时,它会清空缓存,因此第二次调用将始终返回空值。你可以这样做:将 ReadAll 的结果保存下来,并在你的函数中重复使用。例如:
bytes, _ := ioutil.ReadAll(r);
log.Println(string(bytes))

8
如果读者包含非常大的数据,比如2G字节,那么这种方法就不太可行。 - iwind
正如名字所说,“ReadAll”就是要确保你不会耗尽内存。在许多情况下,如果你只需要读取几兆字节的数据,ReadAll就足够了,不需要使用流的复杂性。 - undefined

1

从技术上讲,在一个读取器上,您不能多次阅读。

  • 即使创建不同的引用,
  • 当您阅读一次时,所有引用都将引用相同的对象。
  • 因此,您可以读取内容并将其存储在一个变量中。
  • 然后随意使用该变量。

这将打印两次。

package main

import (
    "io/ioutil"
    "log"
    "strings"
)

func main() {
    r := strings.NewReader("some io.Reader stream to be read\n")
    stringData, _ := ioutil.ReadAll(r)
    log.Println(stringData)
    log.Println(stringData)
}

我想知道为什么不可以 :-) - Abhishek Soni
6
一个io.Reader是一个流。一旦你读完了这个流,就没有更多的内容了。如果你只读取了部分流,下一次读取会从你离开的地方继续进行。 - JimB

0

克隆Reader结构体。

package main

import (
    "io/ioutil"
    "log"
    "strings"
)

func main() {
    r := strings.NewReader("some io.Reader stream to be read\n")
    v := new(strings.Reader)
    *v = *r
    log.Println(ioutil.ReadAll(r))
    log.Println(ioutil.ReadAll(v))

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接