
本教程详细介绍了如何在golang中实现文件下载或数据流处理时的实时字节计数与进度监控。通过创建一个自定义的io.reader包装器,我们可以在数据传输过程中拦截并记录每次读取的字节数,从而实时显示下载进度,而非仅在操作完成后获取最终结果。
在Go语言中,io.Reader和io.Writer是核心的I/O抽象接口,它们分别定义了Read和Write方法。这种接口设计使得Go的I/O操作具有高度的灵活性和可组合性。当我们需要在数据流传输过程中执行额外操作(例如计算已传输字节数、校验数据或进行数据转换)时,Go的组合式设计模式便能派上用场。我们可以创建一个结构体,它嵌入(或包含)一个现有的io.Reader或io.Writer,并实现自己的Read或Write方法,在这个方法中,我们先执行自定义逻辑,然后将调用转发给底层的Reader/Writer。
为了实时监控文件下载的字节数,我们将创建一个名为ProgressReader的自定义io.Reader。这个结构体将包装原始的数据源(例如HTTP响应体),并在每次读取操作时更新已传输的总字节数,并可选地打印当前进度。
首先,定义ProgressReader结构体及其Read方法:
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
"strings"
"time" // 用于控制打印频率
)
// ProgressReader 包装一个 io.Reader,用于跟踪读取的字节数。
type ProgressReader struct {
io.Reader
totalRead int64 // 已读取的总字节数
name string // 阅读器名称,用于打印区分
totalSize int64 // 总文件大小 (可选,用于计算百分比)
lastPrint time.Time // 上次打印时间,用于控制打印频率
}
// NewProgressReader 创建一个新的 ProgressReader 实例。
func NewProgressReader(reader io.Reader, name string, totalSize int64) *ProgressReader {
return &ProgressReader{
Reader: reader,
name: name,
totalSize: totalSize,
lastPrint: time.Now(),
}
}
// Read 方法 '重写' 了底层 io.Reader 的 Read 方法。
// io.Copy 会调用此方法,我们在这里跟踪字节计数并转发调用。
func (pr *ProgressReader) Read(p []byte) (int, error) {
n, err := pr.Reader.Read(p)
pr.totalRead += int64(n)
// 控制打印频率,避免频繁输出导致性能问题或日志过多
// 或者在文件末尾 (io.EOF) 时强制打印最后一次进度
if time.Since(pr.lastPrint) > 100*time.Millisecond || err == io.EOF {
if pr.totalSize > 0 {
progress := float64(pr.totalRead) / float64(pr.totalSize) * 100
fmt.Printf("\r[%s] 已下载: %d / %d 字节 (%.2f%%)", pr.name, pr.totalRead, pr.totalSize, progress)
} else {
fmt.Printf("\r[%s] 已下载: %d 字节", pr.name, pr.totalRead)
}
pr.lastPrint = time.Now()
}
// 下载完成时换行,使最终的完成信息显示在新行
if err == io.EOF {
fmt.Println()
}
return n, err
}
func main() {
// 示例1: 使用内存数据模拟进度跟踪
fmt.Println("--- 示例1: 内存数据进度跟踪 ---")
var src io.Reader
var dst bytes.Buffer
// 创建一些随机输入数据作为源
testData := strings.Repeat("Some random input data for testing progress. ", 100)
src = bytes.NewBufferString(testData)
dataSize := int64(len(testData))
// 使用我们的自定义 ProgressReader 包装源 Reader
progressSrc := NewProgressReader(src, "内存源", dataSize)
count, err := io.Copy(&dst, progressSrc)
if err != nil {
fmt.Println("复制内存数据时出错:", err)
os.Exit(1)
}
fmt.Printf("内存数据传输完成,总计 %d 字节\n\n", count)
// 示例2: 应用于实际文件下载
fmt.Println("--- 示例2: 实际文件下载进度跟踪 ---")
// 替换为实际可用的下载链接,例如一个测试文件。
// 注意:某些链接可能需要处理重定向或SSL证书问题。
downloadURL := "https://speed.hetzner.de/100MB.bin" // 一个100MB的测试文件
outputFileName := "downloaded_file.bin"
fmt.Printf("开始下载文件: %s\n", downloadURL)
// 1. 发起HTTP GET请求
resp, err := http.Get(downloadURL)
if err != nil {
fmt.Println("发起HTTP请求失败:", err)
return
}
defer resp.Body.Close() // 确保关闭响应体
if resp.StatusCode != http.StatusOK {
fmt.Printf("下载失败,HTTP状态码: %d\n", resp.StatusCode)
return
}
// 2. 获取文件总大小 (如果可用)
var totalSize int64
if resp.ContentLength > 0 {
totalSize = resp.ContentLength
fmt.Printf("文件总大小: %d 字节\n", totalSize)
} else {
fmt.Println("无法获取文件总大小,将显示已下载字节数。")
}
// 3. 创建输出文件
outFile, err := os.Create(outputFileName)
if err != nil {
fmt.Println("创建输出文件失败:", err)
return
}
defer outFile.Close() // 确保关闭文件
// 4. 使用 ProgressReader 包装响应体
progressReader := NewProgressReader(resp.Body, "下载器", totalSize)
// 5. 将数据从 ProgressReader 复制到输出文件
downloadedBytes, err := io.Copy(outFile, progressReader)
if err != nil {
fmt.Println("下载文件时出错:", err)
return
}
fmt.Printf("文件下载完成,总计 %d 字节保存到 %s\n", downloadedBytes, outputFileName)
}在上面的代码中:
立即学习“go语言免费学习笔记(深入)”;
要将此ProgressReader应用于实际的文件下载,只需将http.Get返回的resp.Body(它是一个io.ReadCloser,因此也是io.Reader)包装起来即可。上述main函数中的“示例2: 实际文件下载进度跟踪”部分已经展示了这一用法:
以上就是Golang中实现文件下载实时进度监控:自定义io.Reader的实践的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号