对于一个程序,我正在将此函数作为goroutine在for循环中运行,具体取决于传入的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)
}

必须使用整个响应,因此ioutil.ReadAll看起来非常合适,但对可以传递的url数量没有限制,并且ReadAll的性质是它全部存储在内存中,开始感觉不舒服像金票。我是Go的新手,所以如果您决定回答,如果您可以在解决方案背后给出一些解释,将不胜感激!

最佳答案

当我学习如何使用Go时,我得到的一个见解是,对于所有读者来说,ReadAll通常效率低下,并且像您的情况一样,它受制于任意输入量很大并且可能会泄漏内存。开始时,我曾经像这样进行JSON解析:

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

然后,我了解了一种解析JSON的更有效的方法,该方法就是简单地使用Decoder类型。
err := json.NewDecoder(r).Decode(&v)
if err != nil {
    return err
}

这不仅更加简洁,而且在内存方面和时间方面都更加高效:
  • 解码器不必分配巨大的 byte slice 来容纳读取的数据-它可以简单地重用一个微小的缓冲区,该缓冲区将用于Read方法以获取所有数据并进行解析。这样可以节省大量的分配时间,并消除了GC
  • 带来的压力
  • JSON解码器可以在第一个数据块传入后立即开始解析数据-不必等待所有内容完成下载。

  • 现在,您的问题当然与JSON无关,但是此示例非常有用,它说明了如果您可以直接使用Read并一次解析数据块,则可以这样做。特别是对于HTTP请求,解析比读取/下载要快,因此这可能导致解析的数据几乎在请求主体到达时立即准备就绪。

    就您而言,您目前似乎实际上并未对数据进行任何处理,因此没有太多建议可以帮助您。但是io.Readerio.Writer接口(interface)与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)
    

    将所有内容打印到标准输出:
    io.Copy(os.Stdout, resp.Body)
    

    将响应的主体传递给请求的主体:
    resp, err := http.NewRequest("POST", "https://example.com", resp.Body)
    

    关于http - 替代ioutil.ReadAll吗?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/52539695/

    10-09 15:20