一、日常下载图片到本地
cpp
//下载文件
func downloadfile(url, filename string) {
r, err := http.Get(url)
if err != nil {
fmt.Println("err", err.Error())
}
defer r.Body.Close()
f, err := os.Create(filename)
if err != nil {
fmt.Println("err", err.Error())
}
defer f.Close()
n, err := io.Copy(f, r.Body)
fmt.Println(n, err)
}
func main() {
var url = "https://img-s-msn-com.akamaized.net/tenant/amp/entityid/AA1jjNfg.img?w=1920&h=1080&q=60&m=2&f=jpg"
downloadfile(url, "test.jpg")
}
这里以一张图片为例子
从后台可以看到图片的url地址
修改main函数中的url地址,可以下载到本地
结果如下
二、显示文件下载进度
cpp
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func downloadFile(url, filename string) {
r, err := http.Get(url)
if err != nil {
panic(err)
}
defer func() {_ = r.Body.Close()}()
f, err := os.Create(filename)
if err != nil {
panic(err)
}
defer func() {_ = f.Close()}()
n, err := io.Copy(f, r.Body)
fmt.Println(n, err)
}
type Reader struct {
io.Reader
Total int64
Current int64
}
func (r *Reader) Read(p []byte) (n int, err error){
n, err = r.Reader.Read(p)
r.Current += int64(n)
fmt.Printf("\r进度 %.2f%%", float64(r.Current * 10000/ r.Total)/100)
return
}
func DownloadFileProgress(url, filename string) {
r, err := http.Get(url)
if err != nil {
panic(err)
}
defer func() {_ = r.Body.Close()}()
f, err := os.Create(filename)
if err != nil {
panic(err)
}
defer func() {_ = f.Close()}()
reader := &Reader{
Reader: r.Body,
Total: r.ContentLength,
}
_, _ = io.Copy(f, reader)
}
func main() {
// 自动文件下载,比如自动下载图片、压缩包
url := "https://user-gold-cdn.xitu.io/2019/6/30/16ba8cb6465a6418?w=826&h=782&f=png&s=279620"
filename := "poloxue.png"
DownloadFileProgress(url, filename)
}
io.copy函数实现原理
cpp
func Copy(dst Writer, src Reader) (written int64, err error) {
// Copy 函数 调用了 copyBuffer 函数来实现
return copyBuffer(dst, src, nil)
}
func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
// 如果 源Reader 实现了 WriterTo 接口,直接调用该方法 将数据写入到 目标Writer 当中
if wt, ok := src.(WriterTo); ok {
return wt.WriteTo(dst)
}
// 同理,如果 目标Writer 实现了 ReaderFrom 接口,直接调用ReadFrom方法
if rt, ok := dst.(ReaderFrom); ok {
return rt.ReadFrom(src)
}
// 如果没有传入缓冲区,此时默认 创建一个 缓冲区
if buf == nil {
// 默认缓冲区 大小为 32kb
size := 32 * 1024
// 如果源Reader 为LimitedReader, 此时比较 可读数据数 和 默认缓冲区,取较小那个
if l, ok := src.(*LimitedReader); ok && int64(size) > l.N {
if l.N < 1 {
size = 1
} else {
size = int(l.N)
}
}
buf = make([]byte, size)
}
for {
// 调用Read方法 读取数据
nr, er := src.Read(buf)
if nr > 0 {
// 将数据写入到 目标Writer 当中
nw, ew := dst.Write(buf[0:nr])
// 判断写入是否 出现了 错误
if nw < 0 || nr < nw {
nw = 0
if ew == nil {
ew = errInvalidWrite
}
}
// 累加 总写入数据
written += int64(nw)
if ew != nil {
err = ew
break
}
// 写入字节数 小于 读取字节数,此时报错
if nr != nw {
err = ErrShortWrite
break
}
}
if er != nil {
if er != EOF {
err = er
}
break
}
}
return written, err
}func Copy(dst Writer, src Reader) (written int64, err error) {
// Copy 函数 调用了 copyBuffer 函数来实现
return copyBuffer(dst, src, nil)
}
func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
// 如果 源Reader 实现了 WriterTo 接口,直接调用该方法 将数据写入到 目标Writer 当中
if wt, ok := src.(WriterTo); ok {
return wt.WriteTo(dst)
}
// 同理,如果 目标Writer 实现了 ReaderFrom 接口,直接调用ReadFrom方法
if rt, ok := dst.(ReaderFrom); ok {
return rt.ReadFrom(src)
}
// 如果没有传入缓冲区,此时默认 创建一个 缓冲区
if buf == nil {
// 默认缓冲区 大小为 32kb
size := 32 * 1024
// 如果源Reader 为LimitedReader, 此时比较 可读数据数 和 默认缓冲区,取较小那个
if l, ok := src.(*LimitedReader); ok && int64(size) > l.N {
if l.N < 1 {
size = 1
} else {
size = int(l.N)
}
}
buf = make([]byte, size)
}
for {
// 调用Read方法 读取数据
nr, er := src.Read(buf)
if nr > 0 {
// 将数据写入到 目标Writer 当中
nw, ew := dst.Write(buf[0:nr])
// 判断写入是否 出现了 错误
if nw < 0 || nr < nw {
nw = 0
if ew == nil {
ew = errInvalidWrite
}
}
// 累加 总写入数据
written += int64(nw)
if ew != nil {
err = ew
break
}
// 写入字节数 小于 读取字节数,此时报错
if nr != nw {
err = ErrShortWrite
break
}
}
if er != nil {
if er != EOF {
err = er
}
break
}
}
return written, err
}
从中可以看出,io.copy
函数是通过read读取文件进行写入新创建的文件,因此,重写后的read
函数除了实现原来的read
功能,还增加了进度条功能。