Java|进度条工具

#Java [字体 ··]

有时候批量处理任务无法观察执行情况,例如处理进度、处理数量、耗时等,这种“不确定性”会隐藏 bug,等到出问题就晚了。今天,这篇文章的“主角” —— 进度条工具 —— 就是为了解决这个问题。

进度条工具输出预览:

 1[4B645A43] 远程请求记录: [####                ] - 22.91% (9835/42920), 耗时: 2m32s
 2   (1)        (2)               (3)               (4)       (5)          (6)
 3
 4各部分说明:
 5
 6(1) Trace ID
 7(2) 进度条名称
 8(3) 进度条面板
 9(4) 执行占比
10(5) 执行数量细节
11(6) 执行耗时

实现比较简单,源码如下:

 1public class ProgressBar {
 2    private final String name;
 3    private int begin;
 4    private final int end;
 5    private final int step;
 6    private final int printIntervalMills; // 打印间隔, 毫秒
 7    private final long startMills;
 8    private long lastPrintMills;
 9    private final String traceId;
10    private final Logger log;
11    // "[%s] %s: %s - %s (%d/%d), 耗时: %s"
12    private static final String TPL = "[%s] %s: %s - %s (%d/%d), 耗时: %s";
13    private static final String PGS = "[####################][                    ]";
14
15    /** 默认打印间隔 5s */
16    public static ProgressBar of(String name, int total) {
17        return new ProgressBar(name, total, 1, 5000, null);
18    }
19
20    public static ProgressBar of(String name, int total, int printIntervalMills) {
21        return new ProgressBar(name, total, 1, printIntervalMills, null);
22    }
23
24    /** 默认打印间隔 5s */
25    public static ProgressBar of(String name, int total, Logger log) {
26        return new ProgressBar(name, total, 1, 5000, log);
27    }
28
29    public static ProgressBar of(String name, int total, int printIntervalMills, Logger log) {
30        return new ProgressBar(name, total, 1, printIntervalMills, log);
31    }
32
33    public ProgressBar(String name, int total, int step, int printIntervalMills, Logger log) {
34        this.begin = 0;
35        this.end = Math.max(1, total); // >= 1 & 分母 不为0
36        this.step = Math.max(1, step); // >= 1
37        this.printIntervalMills = Math.max(0, printIntervalMills); // >= 0
38        this.name = name != null ? name : "";
39        this.log = log;
40        lastPrintMills = startMills = System.currentTimeMillis();
41        traceId = Integer.toHexString((int) startMills).toUpperCase();
42    }
43
44    public void nextPrint() {
45        next();
46        print();
47    }
48
49    public void next() {
50        begin = Math.min(begin + step, end); // 预防 begin 溢出 end
51    }
52
53    public void print() {
54        if (System.currentTimeMillis() - lastPrintMills < printIntervalMills) return;
55        immediatePrint();
56    }
57
58    public void immediatePrint() {
59        Object[] args = new Object[]{traceId, name, makeProgress(), calcPercent() + "%", begin, end, makeSpendTime()};
60        if (log != null)
61            log.info(String.format(TPL, args));
62        else
63            System.out.printf(TPL + "\n", args);
64        lastPrintMills = System.currentTimeMillis();
65    }
66
67    // ~ private method
68    // --------------------------------------------------------------------------------------------
69    private String makeProgress() {
70        int per = (int) (calcPercent() * 2 / 10); // per <= 20
71        // e.g. [##############      ]
72        return PGS.substring(0, per + 1).concat(PGS.substring(23 + per)); // 23: PGS.length / 2 + 1
73    }
74
75    private String makeSpendTime() {
76        int n = (int) ((System.currentTimeMillis() - startMills) / 1000);
77        int h = n / 3600, m = (n % 3600) / 60, s = n % 60;
78        String time = "";
79        if (h > 0) time += (h + "h");
80        if (m > 0) time += (m + "m");
81        if (s > 0) time += (s + "s");
82        return time.isEmpty() ? "0s" : time; // e.g. 1h23m15s、23m15s、15s
83    }
84
85    private float calcPercent() {
86        float per = (begin * 1.0F) / end;
87        if (1.0F - per < 0.00001F) return 100.0F;
88        return Math.round(per * 10000) / 100.0F;
89    }
90}

使用例子:

 1// Case 1: 每5s打印一次进度条
 2
 3ProgressBar pgs = ProgressBar.of("远程请求记录", size, 5000);
 4for (Request req: reqs) {
 5    // 1)递增步长;2)在满足打印间隔时间后打印进度条
 6    pgs.nextPrint();
 7
 8    // TODO
 9}
10
11// Case 2: 分为两步,每5s打印一次进度条
12ProgressBar pgs2 = ProgressBar.of("远程请求记录", size, 5000);
13for (Request req: reqs) {
14    pgs2.next();// 1)仅递增步长
15    pgs2.print();// 2)在满足打印间隔时间后打印进度条
16
17    // TODO
18}
19
20// Case 3: 立即打印进度条
21ProgressBar pgs3 = ProgressBar.of("远程请求记录", size, 5000);
22for (Request req: reqs) {
23    pgs3.next();// 1)单纯递增步长
24    pgs3.immediatePrint();// 2)忽略打印间隔时间,**立即**打印进度条!
25
26    // TODO
27}

使用注意事项:

  1. 不支持多线程。
  2. 合理打印。频繁打印同样会影响阅读日志,注意评估打印间隔时间。

(完)


博客没有评论系统,可以通过 邮件 评论和交流。 Top↑