如何使用Go高效地下载大文件?

huangapple go评论90阅读模式
英文:

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

  1. 这是一个示例。https://github.com/thbar/golang-playground/blob/master/download-files.go

  2. 我还给你一些可能会帮到你的代码。

代码:

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)
}
}
}
英文:
  1. Here is a sample. https://github.com/thbar/golang-playground/blob/master/download-files.go

  2. 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)
}
}
}

huangapple
  • 本文由 发表于 2012年7月28日 01:38:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/11692860.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定