如何使用Go有效地下载大文件?


106

有没有一种使用Go下载大文件的方法,该方法会将内容直接存储到文件中,而不是将全部内容存储到内存中再写入文件?由于文件太大,因此在将其全部写入内存之前将其全部存储在内存中将耗尽所有内存。

Answers:


214

我假设您的意思是通过http下载(为简便起见,省略了错误检查):

import ("net/http"; "io"; "os")
...
out, err := os.Create("output.txt")
defer out.Close()
...
resp, err := http.Get("http://example.com/")
defer resp.Body.Close()
...
n, err := io.Copy(out, resp.Body)

http.Response的主体是阅读器,因此您可以使用带有阅读器的任何功能,例如一次读取一个块,而不是一次读取所有块。在这种情况下,您会io.Copy()做些麻烦的事情。


85
请注意,io.Copy将从输入中读取32kb(最大),并将其写入输出,然后重复。因此,不必担心内存。
Moshe Revah,2012年

如何取消下载进度?
Geln Yang

您可以使用它在给定的超时后取消下载client := http.Client{Timeout: 10 * time.Second,} client.Get("http://example.com/")
Bharath Kumar

55

Steve M的答案更具描述性。

import (
    "os"
    "net/http"
    "io"
)

func downloadFile(filepath string, url string) (err error) {

  // Create the file
  out, err := os.Create(filepath)
  if err != nil  {
    return err
  }
  defer out.Close()

  // Get the data
  resp, err := http.Get(url)
  if err != nil {
    return err
  }
  defer resp.Body.Close()

  // Check server response
  if resp.StatusCode != http.StatusOK {
    return fmt.Errorf("bad status: %s", resp.Status)
  }

  // Writer the body to file
  _, err = io.Copy(out, resp.Body)
  if err != nil  {
    return err
  }

  return nil
}

1
在我的世界中,我实现了一个需要下载文件的DSL ...使Exec()卷曲非常方便,直到遇到一些OS兼容性和chroot问题,因为它是一个明智的安全模型,我确实不希望对其进行配置。因此,U用此代码替换了我的CURL,从而使性能提高了10-15倍。H!
理查德

14

上面选择的答案io.Copy正是您所需要的,但是如果您对其他功能感兴趣,例如恢复中断的下载,自动命名文件,校验和验证或监视多次下载的进度,请检出抓包


您是否可以添加代码段以确保在不赞成使用链接的情况下不会丢失信息?
030

-6
  1. 这是一个样本。https://github.com/thbar/golang-playground/blob/master/download-files.go

  2. 我也给你一些代码可能会帮助你。

码:

func HTTPDownload(uri string) ([]byte, error) {
    fmt.Printf("HTTPDownload From: %s.\n", uri)
    res, err := http.Get(uri)
    if err != nil {
        log.Fatal(err)
    }
    defer res.Body.Close()
    d, err := ioutil.ReadAll(res.Body)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("ReadFile: Size of download: %d\n", len(d))
    return d, err
}

func WriteFile(dst string, d []byte) error {
    fmt.Printf("WriteFile: Size of download: %d\n", len(d))
    err := ioutil.WriteFile(dst, d, 0444)
    if err != nil {
        log.Fatal(err)
    }
    return err
}

func DownloadToFile(uri string, dst string) {
    fmt.Printf("DownloadToFile From: %s.\n", uri)
    if d, err := HTTPDownload(uri); err == nil {
        fmt.Printf("downloaded %s.\n", uri)
        if WriteFile(dst, d) == nil {
            fmt.Printf("saved %s as %s\n", uri, dst)
        }
    }
}

13
本示例使用读取全部内容到内存中ioutil.ReadAll()。很好,只要您要处理的是小文件。
eduncan911

13
@ eduncan911,但是对于这个明确讨论大型文件并且不想将其全部吸收到内存中的问题来说,这不是很好。
Dave C

2
完全正确,这就是为什么我这么评论-让其他人也知道不要将其用于大文件。
eduncan911

4
这不是一个良性答案,实际上应该删除它。在一大堆代码中使用ReadAll是一个潜在问题,需要等到使用大文件后才能解决。发生的情况是,如果大文件上有ReadAll,通常的响应是伴随着高内存消耗和增加的AWS账单直到出现故障。到发现问题时,账单已经很高了。
罗布
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.