Goを使用して大きなファイルを効率的にダウンロードするにはどうすればよいですか?


106

Goを使用して、ファイルに書き込む前にコンテンツをすべてメモリに保存するのではなく、ファイルに直接保存する大きなファイルをダウンロードする方法はありますか?ファイルは非常に大きいため、ファイルに書き込む前にすべてをメモリに保存すると、すべてのメモリが消費されます。

回答:


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

スティーブ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を実装しました...賢明なセキュリティモデルであるため、実際に構成したくないOSの互換性とchrootの問題に陥るまで、Exec()カールが便利でした。したがって、CURLをこのコードに置き換えると、パフォーマンスが10〜15倍向上します。ああ!
Richard

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 2015年

13
@ eduncan911、しかし、大きなファイルについて明示的に話し、すべてをメモリに吸い込みたくないというこの質問には問題ありません。
Dave C

2
まさにそうです、それで私はそうコメントしました-他の人が知っているだけでなく、これを大きなファイルに使用しないことを知っているためです。
eduncan911 2015年

4
これは無害な答えではないため、実際には削除する必要があります。大量のコードの中でReadAllを使用することは、大きなファイルが使用されるまで待機する潜在的な問題です。何が起こるかというと、大きなファイルにReadAllがある場合、通常の応答は、メモリ消費量が高く、何かが失敗するまでAWSの請求額が増えることです。問題が発見されたときには、法案はすでに高くなっています。
Rob
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.