进度条教程【github.com/cheggaaa/pb】


学习目标:

  • 了解进度条运行原理
  • 掌握github.com/cheggaaa/pb第三方依赖的函数
  • 实践一个进度条

学习内容:

前置说明:

本篇文章所讲解的进度条是由github.com/cheggaaa/pb依赖包提供的。

https://github.com/cheggaaa/pb作者官方文档,请点击

为什么要用https://github.com/cheggaaa/pb依赖包?

  1. https://github.com/cheggaaa/pb是基于golang语言开发的,可以与golang语言开发的项目直接兼容。
  2. https://github.com/cheggaaa/pb中有大量的函数,可以丰富进度条
一个简单的进度条案列
package main

import (
	"time"

	"github.com/cheggaaa/pb"
)

func main() {
	//总量 一般是不变的定值
	count := 1000
	//创建一个总量是5000的进度条
	bar := pb.New(count)

	// 显示百分比
	bar.ShowPercent = true

	// 显示进度条
	bar.ShowBar = true

	//显示 [1/5000]
	bar.ShowCounters = true

	//在进度条右侧显示时间 500ms 【Left是左的意思,为这么显示在右侧。我也不知道,不去纠结】
	bar.ShowTimeLeft = true

	// 进度条启动
	bar.Start()
	for i := 0; i < count; i++ {
		//pb.Add(1) 每次执行一次这个函数,进度条的当前值 +1
		bar.Increment()
		//睡眠1秒
		time.Sleep(time.Millisecond)
	}
	//结束进度条 pb.Finish()   并打印信息
	bar.FinishPrint("The End!")
}

效果:
在这里插入图片描述

这种进度条可以运用在做包子的情景上。假设一共要做1000个包子。每做一个包子,就执行一次bar.Increment()操作。这里就可以引入多线程,每个线程触发一次bar.Increment(),进度条就+1.

package main

import (
	"github.com/cheggaaa/pb"
	"sync"
	"time"
)

func main() {
	//总量 一般是不变的定值
	count := 1000
	//创建一个总量是5000的进度条
	bar := pb.New(count)

	// 显示百分比
	bar.ShowPercent = true

	// 显示进度条
	bar.ShowBar = true

	//显示 [1/5000]
	bar.ShowCounters = true

	//在进度条右侧显示时间 500ms 【Left是左的意思,为这么显示在右侧。我页不知道,不去纠结】
	bar.ShowTimeLeft = true

	// 进度条启动
	bar.Start()
	//等待组
	var wg sync.WaitGroup
	//等待总量
	wg.Add(count)
	for i := 0; i < count; i++ {
		go func() {
			//等待总量 -1
			defer wg.Done()
			//pb.Add(1) 每次执行一次这个函数,进度条的当前值 +1
			bar.Increment()
			//睡眠1秒
			time.Sleep(time.Millisecond)
		}()
	}
	//只有等待总量为0,才会放行
	wg.Wait()

	//结束进度条 pb.Finish()   并打印信息
	bar.FinishPrint("The End!")
}

效果:
在这里插入图片描述

在这里,我使用到了sync.WaitGroup等待组技术。sync.WaitGroup与进度条运行规律是相反的。我们看一下他们的改变量的函数源码。

sync.WaitGroup:

// Done decrements the WaitGroup counter by one.
func (wg *WaitGroup) Done() {
	wg.Add(-1)
}

进度条:

// Increment current value
func (pb *ProgressBar) Increment() int {
	return pb.Add(1)
}

总体来说,满足进度条运行必须有如下条件:必须有总量值和进度推动操作。

多个进度条的联合使用
package main

import (
	"github.com/cheggaaa/pb"
	"math/rand"
	"sync"
	"time"
)

func main() {

	// 创建多个进度条 设置前缀 总量为200
	first := pb.New(200).Prefix("First ")
	second := pb.New(200).Prefix("Second ")
	third := pb.New(200).Prefix("Third ")
	// pool类似于进度条的池子 并启动进度条
	pool, err := pb.StartPool(first, second, third)
	if err != nil {
		panic(err)
	}
	// 推动进度
	wg := new(sync.WaitGroup)
	//[]*pb.ProgressBar{first, second, third} 定义了一个进度条为元素的切片
	for _, bar := range []*pb.ProgressBar{first, second, third} {
		wg.Add(1)
		go func(cb *pb.ProgressBar) {
			for n := 0; n < 200; n++ {
				cb.Increment()
				//随机时间
				time.Sleep(time.Millisecond * time.Duration(rand.Intn(100)))
			}
			cb.Finish()
			wg.Done()
		}(bar)
	}
	wg.Wait()
	// 关闭
	pool.Stop()
}

效果:
在这里插入图片描述

多个进度条的联合使用,一般与多线程搭配的。之前的案列是多个线程共同完成1000个包子,而这个案例是多个线程各自完成各自的任务数量。

课外技术点讲解:

  • rand.Intn(100) 随机数生成,0<随机数<100
  • time.Millisecond * time.Duration(rand.Intn(100)) 中的Duration()表示相乘。

说明:因为time.Millisecond 只能与int类型的常量相乘。而rand.Intn(100)是随机数。

进度条在文件Copy、IO流的运行

本机环境测试:

package main

import (
	"fmt"
	"github.com/cheggaaa/pb"
	"io"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
)

func main() {

	//指定文件的起始路径和目标路径 将E:/static/source中的文件 克隆入 E:/static/dest 中
	sourceName, destName := "E:/static/source/hang.txt", "E:/static/dest/hang.txt"

	// check source
	var source io.Reader
	var sourceSize int64

	if strings.HasPrefix(sourceName, "http://") {
		// 当sourceName 是网站地址时
		resp, err := http.Get(sourceName)
		if err != nil {
			fmt.Printf("Can't get %s: %v\n", sourceName, err)
			return
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			fmt.Printf("Server return non-200 status: %v\n", resp.Status)
			return
		}
		i, _ := strconv.Atoi(resp.Header.Get("Content-Length"))
		sourceSize = int64(i)
		source = resp.Body
	} else {
		// sourceName 是本机上的一个目录 打开
		s, err := os.Open(sourceName)
		if err != nil {
			fmt.Printf("Can't open %s: %v\n", sourceName, err)
			return
		}
		defer s.Close()
		// 获取sourceName的目录信息 并 检测是否存在
		sourceStat, err := s.Stat()
		if err != nil {
			fmt.Printf("Can't stat %s: %v\n", sourceName, err)
			return
		}
		//获取存储大小
		sourceSize = sourceStat.Size()
		source = s
	}

	// 创建转移后的路径 我这里是创建好了的,没有创建的,会自动创建
	dest, err := os.Create(destName)
	if err != nil {
		fmt.Printf("Can't create %s: %v\n", destName, err)
		return
	}
	defer dest.Close()

	// 创建进度条 总量为源文件的size | SetUnits(pb.U_BYTES) 设置文件大小的单位 KB
	bar := pb.New(int(sourceSize)).SetUnits(pb.U_BYTES).SetRefreshRate(time.Millisecond * 10)
	bar.ShowSpeed = true
	bar.Start()

	// 创建一个读对象
	reader := bar.NewProxyReader(source)

	// 克隆
	io.Copy(dest, reader)
	//关闭bar
	bar.Finish()
}

效果:
在这里插入图片描述

这是一个文件的Copy操作。自定义原路径和目标路径。可以看出,进度条的总量是源文件的size大小。这里要说一下,我这个文件size很小,所以时间几乎为0.

线上环境测试:

package main

import (
	"fmt"
	"github.com/cheggaaa/pb"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

func main() {

	//指定文件的起始路径和目标路径 将E:/static/source中的文件 克隆入 E:/static/dest 中
	sourceName, destDir := "https://img01.png", "E:/static/dest/"

	// check source
	var source io.Reader
	var sourceSize int64

	if strings.HasPrefix(sourceName, "http://")  || strings.HasPrefix(sourceName, "https://") {
		// 当sourceName 是网站地址时
		resp, err := http.Get(sourceName)
		if err != nil {
			fmt.Printf("Can't get %s: %v\n", sourceName, err)
			return
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			fmt.Printf("Server return non-200 status: %v\n", resp.Status)
			return
		}
		i, _ := strconv.Atoi(resp.Header.Get("Content-Length"))
		sourceSize = int64(i)
		source = resp.Body
	} else {
		// sourceName 是本机上的一个目录 打开
		s, err := os.Open(sourceName)
		if err != nil {
			fmt.Printf("Can't open %s: %v\n", sourceName, err)
			return
		}
		defer s.Close()
		// 获取sourceName的目录信息 并 检测是否存在
		sourceStat, err := s.Stat()
		if err != nil {
			fmt.Printf("Can't stat %s: %v\n", sourceName, err)
			return
		}
		//获取存储大小
		sourceSize = sourceStat.Size()
		source = s
	}

	// 创建转移后的路径 我这里是创建好了的,没有创建的,会自动创建
	destName := destDir + filepath.Base(sourceName)
	dest, err := os.Create(destName)
	if err != nil {
		fmt.Printf("Can't create %s: %v\n", destName, err)
		return
	}
	defer dest.Close()

	// 创建进度条 总量为源文件的size | SetUnits(pb.U_BYTES) 设置文件大小的单位 KB
	bar := pb.New(int(sourceSize)).SetUnits(pb.U_BYTES).SetRefreshRate(time.Millisecond * 10)
	bar.ShowSpeed = true
	bar.Start()

	// 创建一个读对象
	reader := bar.NewProxyReader(source)

	// 克隆
	io.Copy(dest, reader)
	//关闭bar
	bar.Finish()
}

学习总结:

  • 实践成功进度条的使用
  • 后续深入了解github.com/cheggaaa/pb依赖包的函数
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值