golang程序進(jìn)度條實(shí)現(xiàn)示例詳解
引言
最近在工作中寫一個(gè)批處理腳本,令人抓狂的是每次都不知道腳本要跑到啥時(shí)候結(jié)束,于是想到給程序添加個(gè)進(jìn)度條。
逛了一圈,沒找到特別趁手的輪子,本著有手就行的原則,今天簡單地給大家擼一個(gè)終端進(jìn)度條。
原理
終端進(jìn)度條打印的原理是通過輸入\r將光標(biāo)位置移動(dòng)到當(dāng)前行的行首,重新打印一份進(jìn)度信息。
如果是使用\n,則光標(biāo)會(huì)另起一行打印信息。
上才藝
首先從核心功能出發(fā),進(jìn)度條要告訴我的信息有
- 一共要完成多少任務(wù)
- 現(xiàn)在完成了多少任務(wù)
- 到什么時(shí)候才能完成全部任務(wù)
根據(jù)上面的需求
畫了個(gè)大概的樣子長這樣 [█████████████████████████]100/100 [eta]16:33:39
抽象的用戶調(diào)用函數(shù)有3個(gè)
New()新建進(jìn)度條實(shí)例 Done()推進(jìn)進(jìn)度條進(jìn)展 Finish()完成進(jìn)度條
是不是和sync.WaitGroup很像。
調(diào)用代碼
func main() { bar := progress.New(100) for i := 0; i < 100; i++ { time.Sleep(time.Second / 10) bar.Done(1) } bar.Finish() }
所以根據(jù)用戶調(diào)用需求,首先定義進(jìn)度條結(jié)構(gòu)體。
type Bar struct { total int64 // 總進(jìn)度 current int64 // 當(dāng)前進(jìn)度 filler string // 進(jìn)度填充字符 filler_length int64 // 進(jìn)度條長度 time_format string // 進(jìn)度條時(shí)間格式 interval time.Duration // 打印時(shí)間間隔 begin time.Time // 任務(wù)開始時(shí)間 }
然后根據(jù)用戶調(diào)用的函數(shù),給出函數(shù)實(shí)現(xiàn),當(dāng)然這里面加了一些函數(shù)參數(shù)可選項(xiàng)。
可以在初始化實(shí)例的時(shí)候自定義一些元素,比如填充字符,比如時(shí)間格式或者是每隔多少時(shí)間刷新一次進(jìn)度條等等。
// New 新建進(jìn)度條實(shí)例 func New(total int64, opts ...func(*Bar)) *Bar { bar := &Bar{ total: total, filler: "█", filler_length: 25, time_format: "15:04:05", // 2006-01-02T15:04:05 interval: time.Second, begin: time.Now(), } for _, opt := range opts { opt(bar) } // 定時(shí)打印 ticker := time.NewTicker(bar.interval) go func() { for bar.current < bar.total { fmt.Print(bar.get_progress_string())// 打印進(jìn)度 <-ticker.C } }() return bar } // Done 更新完成進(jìn)度 func (bar *Bar) Done(i int64) { bar.current += i } // Finish 完成最后進(jìn)度條 func (bar *Bar) Finish() { fmt.Println(bar.get_progress_string()) } // WithFiller 設(shè)置進(jìn)度條填充字符 func WithFiller(filler string) func(*Bar) { return func(bar *Bar) { if len(bar.filler) != 0 { bar.filler = filler } } }
那么處理完了用戶怎么使用之后,我們就來開始處理怎么給用戶展示進(jìn)度條效果。
要想根據(jù)進(jìn)度填充不同的字符比例,先算進(jìn)度百分比,長下面這樣子。
//get_percent 獲取進(jìn)度百分比,區(qū)間0-100 func (bar *Bar) get_percent() int64 { return bar.current * 100 / bar.total }
因?yàn)槲覀冞M(jìn)度條并不需要那么精確,所有這里都用的是整數(shù)來處理,更方便一些,不用做各種類型轉(zhuǎn)換。
那么拿到百分比之后,就能根據(jù)進(jìn)度條總長度來計(jì)算要填充多少個(gè)█。
接下來算任務(wù)什么時(shí)候完成,這里用的算法是,用當(dāng)前完成了多少個(gè)任務(wù)和花了多少時(shí)間來估算總?cè)蝿?wù)數(shù)的要花費(fèi)多少時(shí)間,得到預(yù)計(jì)什么時(shí)候完成,代碼是這樣子的:
//get_eta 獲取eta時(shí)間 func (bar *Bar) get_eta(now time.Time) string { eta := (now.Unix() - bar.begin.Unix()) * 100 / (bar.get_percent() + 1) return bar.begin.Add(time.Second * time.Duration(eta)).Format(bar.time_format) }
最后,我們來處理下需要在控制臺(tái)打印的字符串,同時(shí)作為非核心需求,我們還想看批處理操作的速度,所以這里用QPS來表達(dá)我們整個(gè)任務(wù)處理的速度。
QPS表達(dá)任務(wù)處理速度
//get_progress_string 獲取打印控制臺(tái)字符串 func (bar *Bar) get_progress_string() string { fills := bar.get_percent() * bar.filler_length / 100 for i := int64(0); i < bar.filler_length; i++ { switch { case i < fills: chunks[i] = bar.filler default: chunks[i] = " " } } now := time.Now() eta := bar.get_eta(now) qps := bar.current / (now.Unix() - bar.begin.Unix() + 1) return fmt.Sprintf("\r[%s]%d/%d [eta]%s [qps]%d ", strings.Join(chunks, ""), bar.current, bar.total, eta, qps) }
最終呈現(xiàn)的效果 [█████████████████████████]100/100 [eta]16:33:39 [qps]9
當(dāng)然,為了更酷炫一點(diǎn),同時(shí)還引入了emoji字符,能夠根據(jù)字符自適應(yīng)地調(diào)整顯示效果。
下面是項(xiàng)目github地址,供大家參考
https://github.com/jony-lee/go-progress-ba
知識(shí)點(diǎn)總結(jié)
下面是知識(shí)點(diǎn)總結(jié)
- 使用\r來將控制臺(tái)光標(biāo)定位到行首實(shí)現(xiàn)行內(nèi)進(jìn)度條刷新。
- 使用函數(shù)可選參數(shù)來實(shí)現(xiàn)用戶自定義設(shè)置。
- 使用函數(shù)time.NewTicker()實(shí)現(xiàn)定時(shí)刷新控制臺(tái)進(jìn)度條。
以上就是golang程序進(jìn)度條實(shí)現(xiàn)示例詳解的詳細(xì)內(nèi)容,更多關(guān)于golang程序進(jìn)度條的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang設(shè)計(jì)模式之責(zé)任鏈模式講解和代碼示例
責(zé)任鏈?zhǔn)且环N行為設(shè)計(jì)模式, 允許你將請(qǐng)求沿著處理者鏈進(jìn)行發(fā)送, 直至其中一個(gè)處理者對(duì)其進(jìn)行處理,本文就詳細(xì)給大家介紹一下Golang 責(zé)任鏈模式,文中有詳細(xì)的代碼示例,需要的朋友可以參考下2023-06-06SingleFlight模式的Go并發(fā)編程學(xué)習(xí)
這篇文章主要為大家介紹了SingleFlight模式的Go并發(fā)編程學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04Go導(dǎo)入不同目錄下包報(bào)錯(cuò)的解決方法
包(package)是多個(gè)Go源碼的集合,是一種高級(jí)的代碼復(fù)用方案,下面這篇文章主要給大家介紹了關(guān)于Go導(dǎo)入不同目錄下包報(bào)錯(cuò)的解決方法,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06go 判斷兩個(gè) slice/struct/map 是否相等的實(shí)例
這篇文章主要介紹了go 判斷兩個(gè) slice/struct/map 是否相等的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12Go中各種newreader和newbuffer的使用總結(jié)
這篇文章主要為大家詳細(xì)介紹了Go語言中各種newreader和newbuffer的使用的相關(guān)資料,文中的示例代碼講解詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴可以了解下2023-11-11golang常用庫之操作數(shù)據(jù)庫的orm框架-gorm基本使用詳解
這篇文章主要介紹了golang常用庫之操作數(shù)據(jù)庫的orm框架-gorm基本使用,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10