golang中文件下载的两种方式

前面对golang中io读取文件的多种方式做了一个总结,这里记录下golang中文件下载保存的两种常用方式

  • 小文件下载保存
    小文件的话,因为http返回的Response.Body 对象是 io.ReadCloser类型,调用io/ioutil包提供的ReadAll可以将其内容全部读取放入一个临时的slice中,然后使用ioutil.WriteFile将内容写入目标文件中。
  • 大文件下载保存
    如果文件很大的,将文件内容全部读入一个临时的slice再一次性写入磁盘将占用大量的内存空间,同时写入效率也比较低。 比较推荐使用bufio包创建一个带缓冲区的writer对象,然后使用io.Copy方法实现 Response.Body这个reader到 writer对象的直接写入, 避免了内存大量占用的情况,同时提高了文件写入效率。

下面给出了两中方式的实现代码,先创建一个下载对象,如果是小文件读取,则可以最终调用downloadStraightly()函数去下载保存文件; 如果是大文件,则推荐使用downloadWithBuf()去下载保存文件。

  • 测试前准备工作
1
2
3
4
创建一个test.txt文件,并在对应路径下使用python启动一个文件服务器,监听端口默认8000
启动方式:
python2:python -m SimpleHTTPServer
python3: python -m http.server
  • 测试完整代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/*
测试前准备工作:
创建一个test.txt文件,并在对应路径下使用python启动一个文件服务器,监听端口默认8000
启动方式:
python2:python -m SimpleHTTPServer
python3: python -m http.server

*/

package main

import (
"bufio"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
)

var (
ErrUnknownDownloadStrategy = errors.New("unknown download strategy")
ErrUrlUnreachable = errors.New(" download url not available")
ErrReadAllFailure = errors.New("ioutil readall  error")
)

// DlFileInfo 代表一个下载对象
type DlFileInfo struct {
url string //文件的网络下载地址
filename string //下载文件的保存绝对路径
filemode os.FileMode //下载文件的保存mode
strategy int //文件下载方式,可选值为1,2
}

func main() {

dlInfo := DlFileInfo{
url: "http://localhost:8000/tet.txt",
filename: "./test",
filemode: 0755,
strategy: 2, // 如果是小文件下载,1、2均可; 如果是大文件,推荐2
}

err := Download(dlInfo)
if err != nil {
fmt.Println("download file failed:", err)
}
}

func Download(df DlFileInfo) error {
if df.strategy == 1 {
// strategy 为1表示先读取全部后直接写入文件
return downloadStraightly(df)
} else if df.strategy == 2 {
// strategy 为2表示使用io.Copy进行基于文件缓冲区的读写
return downloadWithBuf(df)
} else {
return ErrUnknownDownloadStrategy
}
}

// downloadStraightly 先读取全部的文件内容,放入slice,后将slice写入到新文件中
func downloadStraightly(df DlFileInfo) error {
res, err := http.Get(df.url)
if err != nil {
return ErrUrlUnreachable
}
defer res.Body.Close()

// 如果返回值不是200,表示下载失败
if res.StatusCode != 200 {
return errors.New(res.Status)
}

// 如果文件很大,这个resBody slice将占用大量的内存
resBody, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println("readall error:", err)
return err
}

err = ioutil.WriteFile(df.filename, resBody, df.filemode)
return err
}

// downloadWithBuf 使用io.Copy将数据写入到bufio实例化的writer中
func downloadWithBuf(df DlFileInfo) error {

res, err := http.Get(df.url)
if err != nil {
return ErrUrlUnreachable
}
defer res.Body.Close()

// 如果返回值不是200,表示下载失败
if res.StatusCode != 200 {
return errors.New(res.Status)
}

//这里也可以简单使用包装好的函数 os.Create()
f, err := os.OpenFile(df.filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, df.filemode)
if err != nil {
return err
}
defer f.Close()
bufWriter := bufio.NewWriter(f)

_, err = io.Copy(bufWriter, res.Body)
if err != nil {
return err
}

// 这里不要忘记最后把缓冲区中剩余的数据写入磁盘,默认情况下,4096byte后会自动进行一次磁盘写入
// 比如文件为5000byte, flush会将缓冲区中的904byte写入磁盘中
bufWriter.Flush()

return nil
}

参考:
io包-Copy方法说明
ioutil包-WriteFile方法说明
bufio — 缓存IO