ioutil.ReadAll 的替代方法?

fdd*_*mkf 7 io http go

对于一个程序,我让这个函数在 for 循环中作为 goroutine 运行,具体取决于传入的 url 数量(没有设置数量)。

func makeRequest(url string, ch chan<- string, errors map[string]error){
  res, err := http.Get(url)
  if err != nil {
    errors[url] = err
    close(ch)
    return
  }

  defer res.Body.Close()
  body, _ := ioutil.ReadAll(res.Body)
  ch <- string(body)
}
Run Code Online (Sandbox Code Playgroud)

必须使用响应的整个主体,因此 ioutil.ReadAll 似乎是完美的选择,但对可以传入的 url 数量没有限制,并且 ReadAll 的性质是它全部存储在内存中,它开始感觉更少就像金票一样。我对 Go 还很陌生,所以如果你决定回答,如果你能在你的解决方案背后给出一些解释,我们将不胜感激!

How*_*owl 16

当我学习如何使用 Go 时,我得到的一个见解是 ReadAll 对于大型读者来说通常效率低下,就像你的情况一样,任意输入非常大,可能会泄漏内存。刚开始时,我曾经像这样进行 JSON 解析:

data, err := ioutil.ReadAll(r)
if err != nil {
    return err
}
json.Unmarshal(data, &v)
Run Code Online (Sandbox Code Playgroud)

然后,我学到了一种更有效的解析 JSON 的方法,那就是简单地使用Decoder类型。

err := json.NewDecoder(r).Decode(&v)
if err != nil {
    return err
}
Run Code Online (Sandbox Code Playgroud)

这不仅更简洁,而且效率更高,无论是内存方面还是时间方面:

  • 解码器不必分配一个巨大的字节片来容纳数据读取——它可以简单地重用一个小缓冲区,该缓冲区将用于Read获取所有数据并解析它的方法。这节省了大量的分配时间并消除了 GC 的压力
  • JSON 解码器可以在第一块数据进入后立即开始解析数据 - 它不必等待所有内容都完成下载。

现在,当然您的问题与 JSON 无关,但是此示例有助于说明如果您可以Read直接使用并一次解析数据块,请执行此操作。特别是对于 HTTP 请求,解析比读取/下载更快,因此这可能导致解析的数据在请求正文完成到达的那一刻几乎立即准备就绪。

在您的情况下,您现在似乎实际上并没有对数据进行任何处理,因此没有太多建议可以专门帮助您。但是io.Readerio.Writer接口是 UNIX 管道的 Go 等价物,因此您可以在许多不同的地方使用它们:

将数据写入文件:

f, err := os.Create("file")
if err != nil {
    return err 
}
defer f.Close()

// Copy will put all the data from Body into f, without creating a huge buffer in memory
// (moves chunks at a time)
io.Copy(f, resp.Body)
Run Code Online (Sandbox Code Playgroud)

将所有内容打印到标准输出:

io.Copy(os.Stdout, resp.Body)
Run Code Online (Sandbox Code Playgroud)

将响应的正文通过管道传输到请求的正文:

resp, err := http.NewRequest("POST", "https://example.com", resp.Body)
Run Code Online (Sandbox Code Playgroud)


dm0*_*514 3

为了限制您的应用程序正在使用的内存量,常见的方法是读入缓冲区,这应该可以直接解决您的ioutil.ReadAll问题。

go 的bufio包提供了实用程序 ( Scanner),它支持读取直到分隔符,或从输入中读取一行,这与 @Howl 的问题高度相关