Как я могу эффективно загрузить большой файл с помощью 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 - это Reader, поэтому вы можете использовать любые функции, которые принимают Reader, например, чтобы читать фрагмент за раз, а не все сразу. В этом конкретном случае, io.Copy()ворчание делает за вас.

Стив М
источник
86
Обратите внимание, что io.Copyсчитывает 32 КБ (максимум) из ввода и записывает их на вывод, а затем повторяется. Так что не беспокойтесь о памяти.
Моше Рева
как отменить прогресс загрузки?
Гельн Ян
вы можете использовать это, чтобы отменить загрузку по истечении заданного времени ожиданияclient := http.Client{Timeout: 10 * time.Second,} client.Get("http://example.com/")
Бхарат Кумар,
55

Более описательная версия ответа Стива М.

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 () curl, пока я не столкнулся с некоторыми проблемами совместимости с ОС и chroot, которые я действительно не хотел настраивать, потому что это разумная модель безопасности. Итак, U заменил мой CURL этим кодом и получил повышение производительности в 10-15 раз. ДУХ!
Ричард
14

Выбранный выше ответ с использованием io.Copy- это именно то, что вам нужно, но если вас интересуют дополнительные функции, такие как возобновление прерванных загрузок, автоматическое именование файлов, проверка контрольной суммы или мониторинг выполнения нескольких загрузок, ознакомьтесь с пакетом grab .

Райан Армстронг
источник
Не могли бы вы добавить фрагмент кода, чтобы гарантировать, что информация не будет потеряна, если ссылка устареет?
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)
        }
    }
}
TeeTracker
источник
13
В этом примере все содержимое считывается в память с расширением ioutil.ReadAll(). Это нормально, если вы имеете дело с крошечными файлами.
eduncan911
13
@ eduncan911, но это не подходит для этого вопроса, который явно говорит о больших файлах и не хочет засасывать все это в память.
Dave C
2
Совершенно верно, поэтому я так прокомментировал - чтобы другие знали, что не следует использовать это для больших файлов.
eduncan911 01
4
Это не безобидный ответ, и его действительно следует удалить. Использование ReadAll среди большой кучи кода - это скрытая проблема, ожидающая использования большого файла. Что происходит, так это то, что если для больших файлов есть ReadAll, обычно реакция заключается в том, чтобы согласиться с высоким потреблением памяти и увеличением счетов AWS, пока что-то не выйдет из строя. К тому времени, когда проблема обнаружена, счета уже высоки.
Роб