替代ioutil.ReadAll吗?

时间:2018-09-27 14:48:09

标签: http go io

对于一个程序,我正在将此函数作为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的新手,所以如果您决定回答,如果您可以在解决方案背后给出一些解释,将不胜感激!

3 个答案:

答案 0 :(得分:3)

为了进行bound the amount of memory that you're application is using,常见的方法是读入缓冲区,该缓冲区应直接解决您的ioutil.ReadAll问题。

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

答案 1 :(得分:1)

当我学习如何使用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
}

这不仅更加简洁,而且在内存方面和时间方面都更加高效:

  • 解码器不必分配巨大的字节片来容纳读取的数据-它可以简单地重用一个微小的缓冲区,该缓冲区将与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)

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

io.Copy(os.Stdout, resp.Body)

将响应的正文放置到请求的正文中:

resp, err := http.NewRequest("POST", "https://example.com", resp.Body)

答案 2 :(得分:0)

尽管这很简单

这是客户端程序:

package main

import (
    "fmt"
    "net/http"
)

var data []byte

func main() {
    data = make([]byte, 128)

    ch := make(chan string)

    go makeRequest("http://localhost:8080", ch)

    for v := range ch {
        fmt.Println(v)
    }
}

func makeRequest(url string, ch chan<- string) {
    res, err := http.Get(url)
    if err != nil {
        close(ch)
        return
    }
    defer res.Body.Close()
    defer close(ch) //don't forget to close the channel as well

    for n, err := res.Body.Read(data); err == nil; n, err = res.Body.Read(data) {
        ch <- string(data[:n])
    }
}

这是服务程序:

package main

import (
    "net/http"
)

func main() {
    http.HandleFunc("/", hello)
    http.ListenAndServe("localhost:8080", nil)
}

func hello(w http.ResponseWriter, r *http.Request) {
    http.ServeFile(w, r, "movie.mkv")
}