英文:
How can I efficiently download a large file using Go?
问题
有没有一种使用Go语言下载大文件的方法,可以直接将内容存储到文件中,而不是在写入文件之前将其全部存储在内存中?因为文件太大,将其全部存储在内存中再写入文件会使用掉所有的内存。
英文:
Is there a way to download a large file using Go that will store the content directly into a file instead of storing it all in memory before writing it to a file? Because the file is so big, storing it all in memory before writing it to a file is going to use up all the memory.
答案1
得分: 272
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的Body是一个Reader,所以你可以使用任何接受Reader的函数,例如,每次读取一个块而不是一次性读取全部。在这个特定的情况下,io.Copy()
会为你完成大部分工作。
英文:
I'll assume you mean download via http (error checks omitted for brevity):
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)
The http.Response's Body is a Reader, so you can use any functions that take a Reader, to, e.g. read a chunk at a time rather than all at once. In this specific case, io.Copy()
does the gruntwork for you.
答案2
得分: 87
更详细的版本Steve M的答案。
import (
"os"
"net/http"
"io"
)
func downloadFile(filepath string, url string) (err error) {
// 创建文件
out, err := os.Create(filepath)
if err != nil {
return err
}
defer out.Close()
// 获取数据
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
// 检查服务器响应
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("bad status: %s", resp.Status)
}
// 将响应体写入文件
_, err = io.Copy(out, resp.Body)
if err != nil {
return err
}
return nil
}
英文:
A more descriptive version of Steve M's answer.
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
}
答案3
得分: 17
上面选择的答案使用io.Copy
正是你所需要的,但如果你对其他功能感兴趣,比如恢复中断的下载、自动命名文件、校验和验证或监控多个下载的进度,请查看grab包。
英文:
The answer selected above using io.Copy
is exactly what you need, but if you are interested in additional features like resuming broken downloads, auto-naming files, checksum validation or monitoring progress of multiple downloads, checkout the grab package.
答案4
得分: 0
我也认为有一个进度指示器很好,特别是对于较大的文件。所以在实现一个简单的进度指示器时,我想提出我的建议来解决这个问题。(为了简洁起见,大部分错误处理被省略了)。
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
)
func main() {
temp_path := ".tmp"
req, _ := http.NewRequest("GET", "http://212.183.159.230/200MB.zip", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
f, _ := os.OpenFile(temp_path, os.O_CREATE|os.O_WRONLY, 0644)
defer f.Close()
buf := make([]byte, 32*1024)
var downloaded int64
for {
n, err := resp.Body.Read(buf)
if err != nil {
if err == io.EOF {
break
}
log.Fatalf("下载时出错:%v", err)
}
if n > 0 {
f.Write(buf[:n])
downloaded += int64(n)
fmt.Printf("\r下载中... %.2f%%", float64(downloaded)/float64(resp.ContentLength)*100)
}
}
os.Rename(temp_path, "wordpress.zip")
}
为了使用io.Copy
,我们可以实现一个io.Reader
。这可能是在实际情况下首选的方法,以使其可重用并更容易进行测试。所以这是第二个版本:
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"time"
)
type ProgressReader struct {
Reader io.Reader
Size int64
Pos int64
}
func (pr *ProgressReader) Read(p []byte) (int, error) {
n, err := pr.Reader.Read(p)
if err == nil {
pr.Pos += int64(n)
fmt.Printf("\r下载中... %.2f%%", float64(pr.Pos)/float64(pr.Size)*100)
}
return n, err
}
func main() {
start := time.Now().UnixMilli()
tempPath := ".tmp"
outPath := "200MB.zip"
req, _ := http.NewRequest("GET", "http://212.183.159.230/200MB.zip", nil)
resp, _ := http.DefaultClient.Do(req)
if resp.StatusCode != 200 {
log.Fatalf("下载时出错:%v", resp.StatusCode)
}
defer resp.Body.Close()
f, _ := os.OpenFile(tempPath, os.O_CREATE|os.O_WRONLY, 0644)
defer f.Close()
progressReader := &ProgressReader{
Reader: resp.Body,
Size: resp.ContentLength,
}
if _, err := io.Copy(f, progressReader); err != nil {
log.Fatalf("下载时出错:%v", err)
}
os.Rename(tempPath, outPath)
fmt.Println(" - 下载完成!")
fmt.Printf("耗时:%.2fs\n", float64(time.Now().UnixMilli()-start)/1000)
}
英文:
I also think it's nice to have a progress indicator, especially for larger files. So I want to throw in my two cents for a solution to this problem while implementing a simple progress indicator.
(Most error handling also omitted for brevety).
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
)
func main() {
temp_path := ".tmp"
req, _ := http.NewRequest("GET", "http://212.183.159.230/200MB.zip", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
f, _ := os.OpenFile(temp_path, os.O_CREATE|os.O_WRONLY, 0644)
defer f.Close()
buf := make([]byte, 32*1024)
var downloaded int64
for {
n, err := resp.Body.Read(buf)
if err != nil {
if err == io.EOF {
break
}
log.Fatalf("Error while downloading: %v", err)
}
if n > 0 {
f.Write(buf[:n])
downloaded += int64(n)
fmt.Printf("\rDownloading... %.2f%%", float64(downloaded)/float64(resp.ContentLength)*100)
}
}
os.Rename(temp_path, "wordpress.zip")
}
To use io.Copy
we can implement an io.Reader
. Which probably will be the preferred approach in a real world scenario to make it reusable and easier to test. So here is the second version:
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"time"
)
type ProgressReader struct {
Reader io.Reader
Size int64
Pos int64
}
func (pr *ProgressReader) Read(p []byte) (int, error) {
n, err := pr.Reader.Read(p)
if err == nil {
pr.Pos += int64(n)
fmt.Printf("\rDownloading... %.2f%%", float64(pr.Pos)/float64(pr.Size)*100)
}
return n, err
}
func main() {
start := time.Now().UnixMilli()
tempPath := ".tmp"
outPath := "200MB.zip"
req, _ := http.NewRequest("GET", "http://212.183.159.230/200MB.zip", nil)
resp, _ := http.DefaultClient.Do(req)
if resp.StatusCode != 200 {
log.Fatalf("Error while downloading: %v", resp.StatusCode)
}
defer resp.Body.Close()
f, _ := os.OpenFile(tempPath, os.O_CREATE|os.O_WRONLY, 0644)
defer f.Close()
progressReader := &ProgressReader{
Reader: resp.Body,
Size: resp.ContentLength,
}
if _, err := io.Copy(f, progressReader); err != nil {
log.Fatalf("Error while downloading: %v", err)
}
os.Rename(tempPath, outPath)
fmt.Println(" - Download completed!")
fmt.Printf("Took: %.2fs\n", float64(time.Now().UnixMilli()-start)/1000)
}
答案5
得分: -6
-
这是一个示例。https://github.com/thbar/golang-playground/blob/master/download-files.go
-
我还给你一些可能会帮到你的代码。
代码:
func HTTPDownload(uri string) ([]byte, error) {
fmt.Printf("HTTPDownload 来自: %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: 下载的大小: %d\n", len(d))
return d, err
}
func WriteFile(dst string, d []byte) error {
fmt.Printf("WriteFile: 下载的大小: %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 来自: %s.\n", uri)
if d, err := HTTPDownload(uri); err == nil {
fmt.Printf("下载完成 %s.\n", uri)
if WriteFile(dst, d) == nil {
fmt.Printf("保存 %s 为 %s\n", uri, dst)
}
}
}
英文:
-
Here is a sample. https://github.com/thbar/golang-playground/blob/master/download-files.go
-
Also I give u some codes might help you.
code:
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)
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论