Як я можу ефективно завантажити великий файл за допомогою 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, наприклад, читати шматок за раз, а не всі одразу. У цьому конкретному випадку io.Copy()чинить вас хрюкання.


85
Зауважте, що io.Copyз вводу зчитується 32 кб (максимум) і записує їх на вихід, а потім повторює. Тож не турбуйтеся про пам’ять.
Moshe Revah

як скасувати хід завантаження?
Гельн Ян

Ви можете скористатися цим способом, щоб скасувати завантаження після заданого тайм-ауту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 (), поки я не потрапив у деякі проблеми з ОС та chroot, які я дійсно не хотів налаштовувати навколо, оскільки це розумна модель безпеки. Тож U замінив мій CURL цим кодом і отримав поліпшення продуктивності в 10-15 разів. DUH!
Річард

14

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


Чи можете ви додати фрагмент коду, щоб забезпечити втрату інформації, якщо посилання застаріло?
030

-6
  1. Ось зразок. https://github.com/thbar/golang-playground/blob/master/download-files.go

  2. Також я даю u деякі коди можуть допомогти вам.

код:

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, але це непогано в цьому питанні, яке прямо говорить про великі файли і не хоче все це забирати в пам'ять.
Дейв C

2
Точно так, тому я так прокоментував - щоб інші знали, як не використовувати це для великих файлів.
eduncan911

4
Це не є доброякісною відповіддю, і її потрібно фактично усунути. Використання ReadAll серед великої купи коду є прихованою проблемою, що чекає, поки не буде використаний великий файл. Що трапляється, це те, що якщо на великих файлах є ReadAll, зазвичай відповідь полягає у великому споживанні пам'яті та збільшенні рахунків AWS, поки щось не виходить з ладу. До моменту виявлення проблеми рахунки вже високі.
Роб
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.