Java 进度条工具
有时候批量处理任务无法观察执行情况,例如处理进度、处理数量、耗时等,这种“不确定性”会隐藏 bug,等到出问题就晚了。今天,这篇文章的“主角” —— 进度条工具 —— 就是为了解决这个问题。
进度条工具输出预览:
[4B645A43] 远程请求记录: [#### ] - 22.91% (9835/42920), 耗时: 2m32s
(1) (2) (3) (4) (5) (6)
各部分说明:
(1) Trace ID
(2) 进度条名称
(3) 进度条面板
(4) 执行占比
(5) 执行数量细节
(6) 执行耗时
实现比较简单,源码如下:
public class ProgressBar {
private final String name;
private int begin;
private final int end;
private final int step;
private final int printIntervalMills; // 打印间隔, 毫秒
private final long startMills;
private long lastPrintMills;
private final String traceId;
private final Logger log;
// "[%s] %s: %s - %s (%d/%d), 耗时: %s"
private static final String TPL = "[%s] %s: %s - %s (%d/%d), 耗时: %s";
private static final String PGS = "[####################][ ]";
/** 默认打印间隔 5s */
public static ProgressBar of(String name, int total) {
return new ProgressBar(name, total, 1, 5000, null);
}
public static ProgressBar of(String name, int total, int printIntervalMills) {
return new ProgressBar(name, total, 1, printIntervalMills, null);
}
/** 默认打印间隔 5s */
public static ProgressBar of(String name, int total, Logger log) {
return new ProgressBar(name, total, 1, 5000, log);
}
public static ProgressBar of(String name, int total, int printIntervalMills, Logger log) {
return new ProgressBar(name, total, 1, printIntervalMills, log);
}
public ProgressBar(String name, int total, int step, int printIntervalMills, Logger log) {
this.begin = 0;
this.end = Math.max(1, total); // >= 1 & 分母 不为0
this.step = Math.max(1, step); // >= 1
this.printIntervalMills = Math.max(0, printIntervalMills); // >= 0
this.name = name != null ? name : "";
this.log = log;
lastPrintMills = startMills = System.currentTimeMillis();
traceId = Integer.toHexString((int) startMills).toUpperCase();
}
public void nextPrint() {
next();
print();
}
public void next() {
begin = Math.min(begin + step, end); // 预防 begin 溢出 end
}
public void print() {
if (System.currentTimeMillis() - lastPrintMills < printIntervalMills) return;
immediatePrint();
}
public void immediatePrint() {
Object[] args = new Object[]{traceId, name, makeProgress(), calcPercent() + "%", begin, end, makeSpendTime()};
if (log != null)
log.info(String.format(TPL, args));
else
System.out.printf(TPL + "\n", args);
lastPrintMills = System.currentTimeMillis();
}
// ~ private method
// --------------------------------------------------------------------------------------------
private String makeProgress() {
int per = (int) (calcPercent() * 2 / 10); // per <= 20
// e.g. [############## ]
return PGS.substring(0, per + 1).concat(PGS.substring(23 + per)); // 23: PGS.length / 2 + 1
}
private String makeSpendTime() {
int n = (int) ((System.currentTimeMillis() - startMills) / 1000);
int h = n / 3600, m = (n % 3600) / 60, s = n % 60;
String time = "";
if (h > 0) time += (h + "h");
if (m > 0) time += (m + "m");
if (s > 0) time += (s + "s");
return time.isEmpty() ? "0s" : time; // e.g. 1h23m15s、23m15s、15s
}
private float calcPercent() {
float per = (begin * 1.0F) / end;
if (1.0F - per < 0.00001F) return 100.0F;
return Math.round(per * 10000) / 100.0F;
}
}
使用例子:
// Case 1: 每5s打印一次进度条
ProgressBar pgs = ProgressBar.of("远程请求记录", size, 5000);
for (Request req: reqs) {
// 1)递增步长;2)在满足打印间隔时间后打印进度条
pgs.nextPrint();
// TODO
}
// Case 2: 分为两步,每5s打印一次进度条
ProgressBar pgs2 = ProgressBar.of("远程请求记录", size, 5000);
for (Request req: reqs) {
pgs2.next();// 1)仅递增步长
pgs2.print();// 2)在满足打印间隔时间后打印进度条
// TODO
}
// Case 3: 立即打印进度条
ProgressBar pgs3 = ProgressBar.of("远程请求记录", size, 5000);
for (Request req: reqs) {
pgs3.next();// 1)单纯递增步长
pgs3.immediatePrint();// 2)忽略打印间隔时间,**立即**打印进度条!
// TODO
}
使用注意事项:
- 不支持多线程。
- 合理打印。频繁打印同样会影响阅读日志,注意评估打印间隔时间。
(完)