Làm cách nào để tải một tệp lớn xuống một cách hiệu quả bằng Go?


106

Có cách nào để tải xuống một tệp lớn bằng Go sẽ lưu trữ nội dung trực tiếp vào tệp thay vì lưu tất cả vào bộ nhớ trước khi ghi vào tệp không? Bởi vì tệp rất lớn, lưu trữ tất cả trong bộ nhớ trước khi ghi vào tệp sẽ sử dụng hết bộ nhớ.

Câu trả lời:


214

Tôi giả sử ý bạn là tải xuống qua http (bỏ qua kiểm tra lỗi cho ngắn gọn):

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)

Phần thân của http.Response là một Trình đọc, vì vậy bạn có thể sử dụng bất kỳ chức năng nào đưa Trình đọc, chẳng hạn như đọc một đoạn cùng một lúc chứ không phải tất cả cùng một lúc. Trong trường hợp cụ thể này, io.Copy()không có gì khó khăn cho bạn.


85
Lưu ý rằng io.Copyđọc 32kb (tối đa) từ đầu vào và ghi chúng vào đầu ra, sau đó lặp lại. Vì vậy, đừng lo lắng về bộ nhớ.
Moshe Revah

làm thế nào để hủy tiến trình tải xuống?
Geln Yang

bạn có thể sử dụng quyền này để hủy tải xuống sau thời gian chờ nhất địnhclient := http.Client{Timeout: 10 * time.Second,} client.Get("http://example.com/")
Bharath Kumar

55

Một phiên bản mô tả nhiều hơn câu trả lời của 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
Trong vũ trụ của mình, tôi đã triển khai một DSL cần tải xuống tệp ... thật tiện lợi khi Exec () cuộn lại cho đến khi tôi gặp một số vấn đề về tính toán và chroot của hệ điều hành mà tôi thực sự không muốn cấu hình xung quanh vì đó là một mô hình bảo mật hợp lý. Vì vậy, U thay thế CURL của tôi bằng mã này và cải thiện hiệu suất gấp 10-15 lần. TÂT NHIÊN!
Richard

14

Câu trả lời được chọn ở trên bằng cách sử dụng io.Copychính xác những gì bạn cần, nhưng nếu bạn quan tâm đến các tính năng bổ sung như tiếp tục tải xuống bị hỏng, tự động đặt tên tệp, xác thực tổng kiểm tra hoặc theo dõi tiến độ của nhiều lần tải xuống, hãy xem gói lấy .


Bạn có thể thêm đoạn mã để đảm bảo rằng thông tin sẽ không bị mất nếu liên kết không được dùng nữa không?
030

-6
  1. Đây là một mẫu. https://github.com/thbar/golang-playground/blob/master/download-files.go

  2. Ngoài ra, tôi cung cấp cho bạn một số mã có thể giúp bạn.

mã:

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
Ví dụ này đọc toàn bộ nội dung vào bộ nhớ, với ioutil.ReadAll(). Điều đó tốt, miễn là bạn đang xử lý các tệp nhỏ.
eduncan911

13
@ eduncan911, nhưng câu hỏi này nói rõ ràng về các tệp lớn và không muốn đưa tất cả vào bộ nhớ thì không ổn.
Dave C

2
Chính xác là đúng, đó là lý do tại sao tôi nhận xét như vậy - để những người khác biết cũng như không sử dụng điều này cho các tệp lớn.
eduncan911

4
Đây không phải là một câu trả lời lành tính, và thực sự nên được loại bỏ. Việc sử dụng ReadAll giữa một đống lớn mã là một vấn đề tiềm ẩn khi chờ đợi cho đến khi một tệp lớn được sử dụng. Điều xảy ra là nếu có ReadAll trên các tệp lớn, phản hồi thường là đi kèm với việc tiêu thụ bộ nhớ cao và tăng hóa đơn AWS cho đến khi có sự cố nào đó xảy ra. Vào thời điểm vấn đề được phát hiện, các hóa đơn đã cao.
Rob
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.