Go kullanarak büyük bir dosyayı verimli bir şekilde nasıl indirebilirim?


106

Go kullanarak, içeriği bir dosyaya yazmadan önce tümünü bellekte depolamak yerine doğrudan bir dosyaya depolayacak büyük bir dosya indirmenin bir yolu var mı? Dosya çok büyük olduğu için, bir dosyaya yazmadan önce hepsini hafızada saklamak tüm hafızayı kullanacaktır.

Yanıtlar:


214

Http üzerinden indirmeyi kastettiğinizi varsayacağım (kısa olması için hata kontrolleri atlanmıştır):

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'nin Gövdesi bir Okuyucu'dur, bu nedenle, bir Okuyucuya götüren herhangi bir işlevi, örneğin bir kerede okumak yerine her seferinde bir bölümü okumak için kullanabilirsiniz. Bu özel durumda, io.Copy()homurtu sizin için çalışıyor.


85
Not io.Copygirişten 32 KB (maksimum) okur ve çıkışına bunları yazıyor, sonra tekrarlar. Bu yüzden hafıza konusunda endişelenme.
Moshe Revah

indirme ilerlemesi nasıl iptal edilir?
Geln Yang

bunu verilen zaman aşımından sonra indirmeyi iptal etmek için kullanabilirsinizclient := http.Client{Timeout: 10 * time.Second,} client.Get("http://example.com/")
Bharath Kumar

55

Steve M'nin cevabının daha açıklayıcı bir versiyonu.

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
Benim evrenimde, bir dosyayı indirmesi gereken bir DSL uyguladım ... Bazı işletim sistemi uyumlulukları ve gerçekten yapılandırmak istemediğim chroot sorunlarına düşene kadar Exec () curl için uygun oldu çünkü mantıklı bir güvenlik modeli. Yani CURL'mi bu kodla değiştirip 10-15x performans artışı elde ettik. DUH!
Richard

14

Yukarıda seçilen cevap io.Copytam olarak ihtiyacınız olan şeydir, ancak bozuk indirmeleri sürdürme, dosyaları otomatik adlandırma, sağlama toplamı doğrulama veya birden fazla indirmenin ilerlemesini izleme gibi ek özelliklerle ilgileniyorsanız, kapma paketini kontrol edin .


Bağlantının kullanımdan kaldırılması durumunda bilgilerin kaybolmamasını sağlamak için bir kod parçası ekleyebilir misiniz?
030

-6
  1. İşte bir örnek. https://github.com/thbar/golang-playground/blob/master/download-files.go

  2. Ayrıca size yardımcı olabilecek bazı kodlar veriyorum.

kod:

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
Bu örnek, tüm içeriği belleğe okur ioutil.ReadAll(). Küçük dosyalarla uğraştığınız sürece sorun değil.
eduncan911

13
@ eduncan911, ancak büyük dosyalardan açıkça bahseden ve hepsini hafızaya almak istemeyen bu soru için uygun değil.
Dave C

2
Kesinlikle doğru, bu yüzden böyle yorum yaptım - başkalarının da bilmesi için bunu büyük dosyalar için kullanmamaları için.
eduncan911

4
Bu zararsız bir cevap değildir ve aslında kaldırılması gerekir. ReadAll'ın büyük bir kod yığını arasında kullanılması, büyük bir dosya kullanılıncaya kadar bekleyen gizli bir sorundur. Büyük dosyalarda ReadAll varsa, genellikle yanıt yüksek bellek tüketimi ve bir şeyler başarısız olana kadar artan AWS faturaları ile devam etmektir. Sorun ortaya çıktığında, faturalar zaten yüksek.
Rob
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.