如何在控制台实现一个进度条

一、前言

在今天使用Java代码做一个集合的任务的时候,没在for循环中手动打印日志信息,导致在任务执行后根本不知道执行到了哪一步。

这点让我挺困扰的,于是在github上寻找有没有什么进度条的显示方式,我还真找到了。

看了一下代码,挺简单的,就将思路直接copy过来,实现了一个自己的控制台进度条。

vdurmont/etaprinter: Java console progress bar (github.com)

二、代码

抽象类ProcessBarUtil.java,里面有着基本的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package com.banmoon.utils.processbar;

import java.util.Iterator;
import java.util.function.Consumer;

/**
* @author banmoon
* @date 2024/03/08 15:52:13
*/
public abstract class ProcessBarUtil<T> implements Iterable<T> {

/**
* 遍历的数据
*/
private final Iterable<T> data;

/**
* 结束数量
*/
private final Integer startNum;

/**
* 当前的进度
*/
private Integer current;

/**
* 结束数量
*/
private final Integer endNum;

/**
* 进度条当前位置
*/
private Integer processCurrentNum = 0;

/**
* 进度条总数
*/
private final Integer processTotalNum = 100;

public ProcessBarUtil(Iterable<T> data, Integer startNum, Integer current, Integer endNum) {
this.data = data;
this.startNum = startNum;
this.current = current;
this.endNum = endNum;
}

/**
* 添加进度
*
* @param num 本次进度数量
*/
public void add(Integer num) {
this.current += num;
this.processCurrentNum = (int) (100.0 * current / endNum);
updateProcessBar(processCurrentNum, processTotalNum);
}

/**
* 更新进度条
*
* @param current 当前进度
*/
public void update(Integer current) {
this.current = current;
this.processCurrentNum = (int) (100.0 * current / endNum);
updateProcessBar(processCurrentNum, processTotalNum);
}

protected abstract void updateProcessBar(Integer processCurrentNum, Integer processTotalNum);

@Override
public Iterator<T> iterator() {
return data.iterator();
}

@Override
public void forEach(Consumer<? super T> action) {
data.forEach(t -> {
action.accept(t);
add(1);
});
}
}

以及它的第一个实现类ConsoleProcessBarUtil.java,使用控制台输出进度条

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package com.banmoon.utils.processbar;

import cn.hutool.core.collection.IterUtil;

import java.io.IOException;
import java.io.OutputStream;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
* 控制台打印输出进度条
*
* @author banmoon
* @date 2024/03/08 16:05:26
*/
public class ConsoleProcessBarUtil<T> extends ProcessBarUtil<T> {

private final OutputStream outputStream;

/**
* 未完成的
*/
private static final char incomplete = '░';

/**
* 完成的
*/
private static final char complete = '█';


private ConsoleProcessBarUtil(Iterable<T> data, Integer startNum, Integer current, Integer endNum) {
super(data, startNum, current, endNum);
outputStream = System.out;
}

public static <T> ProcessBarUtil<T> init(Iterable<T> data) {
return new ConsoleProcessBarUtil<>(data, 0, 0, IterUtil.size(data));
}

public static ProcessBarUtil<?> init(Integer startNum, Integer endNum) {
return new ConsoleProcessBarUtil<>(null, startNum, startNum, endNum);
}

@Override
protected void updateProcessBar(Integer processCurrentNum, Integer processTotalNum) {
String processBar = generateProcessBar(processCurrentNum, processTotalNum);
try {
outputStream.write("\r".getBytes());
outputStream.write(processBar.getBytes());
} catch (IOException e) {
throw new RuntimeException("an error occurred while printing the progress bar");
}
}

private String generateProcessBar(Integer processCurrentNum, Integer processTotalNum) {
StringBuilder sb = new StringBuilder("[");
Stream.generate(() -> complete)
.limit(processCurrentNum)
.forEach(sb::append);
IntStream.range(processCurrentNum, processTotalNum)
.mapToObj(i -> incomplete)
.forEach(sb::append);
sb.append("] ");
if (processCurrentNum >= processTotalNum) {
sb.append("complete\n");
} else {
sb.append(processCurrentNum).append("%");
}
return sb.toString();
}

}

三、测试效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.banmoon.utils.stream;

import cn.hutool.core.util.RandomUtil;
import com.banmoon.utils.processbar.ConsoleProcessBarUtil;
import com.banmoon.utils.processbar.ProcessBarUtil;
import org.junit.Assert;
import org.junit.Test;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class ProcessBarUtilTest {

@Test
public void iterableTest() {
List<Integer> list = IntStream.range(0, 100).boxed().collect(Collectors.toList());
ConsoleProcessBarUtil
.init(list)
.forEach(str -> {
try {
// 模拟业务执行
TimeUnit.MILLISECONDS.sleep(RandomUtil.randomInt(50, 200));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
Assert.assertEquals(list.size(), 100);
}

@Test
public void forTest() {
int current = 50;
int total = 200;
ProcessBarUtil<?> processBarUtil = ConsoleProcessBarUtil.init(current, total);
while (current < total) {
try {
// 模拟业务执行
TimeUnit.MILLISECONDS.sleep(RandomUtil.randomInt(50, 200));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
current++;
processBarUtil.update(current);
}
Assert.assertEquals(current, total);
}


}

运行后查看效果,成功

abc

四、最后

后续会完善加上,执行的耗时,预计多少时间完成等信息。

以及看看除了控制台,还有没有其他实现。

我是半月,你我一同共勉!!!