Java中的命令行进度条

162

我有一个在命令行模式下运行的Java程序,我想显示一个进度条,显示作业完成的百分比。和unix系统下使用wget看到的同样类型的进度条一样。这可能吗?


我不知道你具体问题的答案,但你可以从查看这个问题中提到的库开始:https://dev59.com/oHRC5IYBdhLWcg3wAcbU - Jonik
谢谢。那里引用的库更多是关于命令行参数解析,而不是在控制台中显示。但还是谢谢。 - g andrieu
8
这是相关话题。楼主询问是否可能,而不是寻求图书馆建议(至少在当前编辑中)。 - Neil McGuigan
我尝试了这段代码 https://dev59.com/_3NA5IYBdhLWcg3wVcT6 并且成功运行了!(只能在终端中正确运行,不要在Eclipse中使用) - Wendel
18个回答

209
我以前实现过这样的东西。这不是关于Java,而是关于向控制台发送哪些字符的问题。关键在于 \n 和 \r 之间的区别。\n 进入新行的开头, 而 \r 只是回到同一行的开头。因此,要做的事情是打印进度条,例如,通过打印字符串:
"|========        |\r"
在下一个进度条的时钟周期内,使用更长的进度条覆盖相同的行。(因为我们使用\r,所以保持在同一行)。例如:
"|=========       |\r"

你需要记住的是,在完成后,如果你只是打印

"done!\n"

你的行可能仍然存在进度条的垃圾信息。因此,在使用完进度条后,请确保打印足够的空格以将其从行中移除。例如:

"done             |\n"

这真的是跨平台的吗?在Windows和Linux上它可能表现得不错,但在Mac上呢?我没有一台Mac电脑,所以无法尝试... - Radu Murzea
3
OS X 是一个类 Unix 的操作系统,所以如果在 Linux 上能用,那么在 OS X 上也能用(并非所有情况都适用,但这里是适用的)。 - bfontaine
4
谢谢!但这对于 ant 不起作用(在 Linux 和 OSX 上都试过了,直接调用 Java 时运行正常)。有人有什么想法吗? - user495285
请注意,这在Eclipse内置控制台中不起作用(至少对我来说是这样)。但由于这是一个开发控制台,所以这并不太重要。 - Qw3ry
我找到了这个链接:https://dev59.com/_3NA5IYBdhLWcg3wVcT6 - Wendel
这对我来说完美地解决了问题,只需使用 System.out.print(); 而不是标准的 #println。但是其中的缺点是,我必须在完成/使用进度条后加入一个 #println,否则它会消失! - JumperBot_

98

这里有一个简单的控制台进度条:https://github.com/ctongfei/progressbar,许可证:MIT。

现在进度条的编写在另一个线程上运行。

建议使用Menlo、Fira Mono、Source Code Pro或SF Mono字体以获得最佳视觉效果。

对于Consolas或Andale Mono字体,请使用ProgressBarStyle.ASCII(见下文),因为在这些字体中,绘制框图的字形未对齐。

Maven

<dependency>
  <groupId>me.tongfei</groupId>
  <artifactId>progressbar</artifactId>
  <version>0.5.5</version>
</dependency>

使用方法:

ProgressBar pb = new ProgressBar("Test", 100); // name, initial max
 // Use ProgressBar("Test", 100, ProgressBarStyle.ASCII) if you want ASCII output style
pb.start(); // the progress bar starts timing
// Or you could combine these two lines like this:
//   ProgressBar pb = new ProgressBar("Test", 100).start();
some loop {
  ...
  pb.step(); // step by 1
  pb.stepBy(n); // step by n
  ...
  pb.stepTo(n); // step directly to n
  ...
  pb.maxHint(n);
  // reset the max of this progress bar as n. This may be useful when the program
  // gets new information about the current progress.
  // Can set n to be less than zero: this means that this progress bar would become
  // indefinite: the max would be unknown.
  ...
  pb.setExtraMessage("Reading..."); // Set extra message to display at the end of the bar
}
pb.stop() // stops the progress bar

2
这看起来很整洁,它将允许我替换在我的CLI上有的几百个字符串缓冲区行。但是如果我有一个应用程序,在“ticks”(step()调用之间的分钟数)之间运行几分钟怎么办?你的库是否异步运行,从而允许时钟更新?如果我的库运行了几天怎么办?它是否使用/r策略?这会导致多达几百兆字节的输出吗? - Groostav
1
该库以异步方式运行。我没有检查时间溢出。如果不起作用,请提交问题。通常情况下,它会截断字符串它使用\r策略 - koppor
这个库看起来很棒!只是想知道如何自定义进度条的长度?似乎整行文本,包括初始和额外消息都被应用了固定长度,以便进度条的长度可以动态改变。在Windows 10下使用IDEA进行测试。谢谢! - xman_bsn

27

我发现以下代码能够正常工作。它将字节写入输出缓冲区。也许使用类似 System.out.println() 方法的方法会将\r的出现替换为\n以匹配目标的本地行尾(如果未正确配置)。

public class Main{
    public static void main(String[] arg) throws Exception {
        String anim= "|/-\\";
        for (int x =0 ; x < 100 ; x++) {
            String data = "\r" + anim.charAt(x % anim.length()) + " " + x;
            System.out.write(data.getBytes());
            Thread.sleep(100);
        }
    }
}

13

我已经做了一个百分比进度条,用于检查剩余下载文件。

我会定期调用这个方法,在文件下载过程中检查总文件大小和剩余量,并以 % 的形式呈现出来。

它也可以用于其他任务目的。

测试和输出示例

progressPercentage(0, 1000);
[----------] 0%

progressPercentage(10, 100);
[*---------] 10%

progressPercentage(500000, 1000000);
[*****-----] 50%

progressPercentage(90, 100);
[*********-] 90%

progressPercentage(1000, 1000);
[**********] 100%

用 for 循环测试

for (int i = 0; i <= 200; i = i + 20) {
    progressPercentage(i, 200);
    try {
        Thread.sleep(500);
    } catch (Exception e) {
    }
}

这种方法可以很容易地进行修改:

public static void progressPercentage(int remain, int total) {
    if (remain > total) {
        throw new IllegalArgumentException();
    }
    int maxBareSize = 10; // 10unit for 100%
    int remainProcent = ((100 * remain) / total) / maxBareSize;
    char defaultChar = '-';
    String icon = "*";
    String bare = new String(new char[maxBareSize]).replace('\0', defaultChar) + "]";
    StringBuilder bareDone = new StringBuilder();
    bareDone.append("[");
    for (int i = 0; i < remainProcent; i++) {
        bareDone.append(icon);
    }
    String bareRemain = bare.substring(remainProcent, bare.length());
    System.out.print("\r" + bareDone + bareRemain + " " + remainProcent * 10 + "%");
    if (remain == total) {
        System.out.print("\n");
    }
}

6

这个C#示例我认为在Java中的System.out.print也是一样的。如果我错了,请随意纠正。

基本上,您需要在消息的开头写出\r转义字符, 这将导致光标返回到行的开头(换行符),而不移动到下一行。

    static string DisplayBar(int i)
    {
        StringBuilder sb = new StringBuilder();

        int x = i / 2;
        sb.Append("|");
        for (int k = 0; k < 50; k++)
            sb.AppendFormat("{0}", ((x <= k) ? " " : "="));
        sb.Append("|");

        return sb.ToString();
    }

    static void Main(string[] args)
    {
        for (int i = 0; i <= 100; i++)
        {
            System.Threading.Thread.Sleep(200);
            Console.Write("\r{0} {1}% Done", DisplayBar(i), i);
        }

        Console.ReadLine();

    }

很好的例子,肯定会有帮助。谢谢。 - g andrieu
太好了!谢谢。像个魅力一样运作。易于阅读。可爱。 - kholofelo Maloma
我把它翻译成 Java ,它运行得非常好!! - Salem loress

5

对 @maytham-ɯɐɥʇʎɐɯ 的方法进行了微调和更新,现在它支持任意大小的进度条:

    public static void progressPercentage(int done, int total) {
        int size = 5;
        String iconLeftBoundary = "[";
        String iconDone = "=";
        String iconRemain = ".";
        String iconRightBoundary = "]";

        if (done > total) {
            throw new IllegalArgumentException();
        }
        int donePercents = (100 * done) / total;
        int doneLength = size * donePercents / 100;

        StringBuilder bar = new StringBuilder(iconLeftBoundary);
        for (int i = 0; i < size; i++) {
            if (i < doneLength) {
                bar.append(iconDone);
            } else {
                bar.append(iconRemain);
            }
        }
        bar.append(iconRightBoundary);

        System.out.print("\r" + bar + " " + donePercents + "%");

        if (done == total) {
            System.out.print("\n");
        }
    }

4

我将Eoin Campbell的代码转换成了Java,并添加了格式化的进度百分比。

public static String progressBar(int currentValue, int maxValue) {
    int progressBarLength = 33; //
    if (progressBarLength < 9 || progressBarLength % 2 == 0) {
        throw new ArithmeticException("formattedPercent.length() = 9! + even number of chars (one for each side)");
    }
    int currentProgressBarIndex = (int) Math.ceil(((double) progressBarLength / maxValue) * currentValue);
    String formattedPercent = String.format(" %5.1f %% ", (100 * currentProgressBarIndex) / (double) progressBarLength);
    int percentStartIndex = ((progressBarLength - formattedPercent.length()) / 2);

    StringBuilder sb = new StringBuilder();
    sb.append("[");
    for (int progressBarIndex = 0; progressBarIndex < progressBarLength; progressBarIndex++) {
        if (progressBarIndex <= percentStartIndex - 1
        ||  progressBarIndex >= percentStartIndex + formattedPercent.length()) {
            sb.append(currentProgressBarIndex <= progressBarIndex ? " " : "=");
        } else if (progressBarIndex == percentStartIndex) {
            sb.append(formattedPercent);
        }
    }
    sb.append("]");
    return sb.toString();
}

int max = 22;
System.out.println("Generating report...");
for (int i = 0; i <= max; i++) {
   Thread.sleep(100);
   System.out.print(String.format("\r%s", progressBar(i, max)));
}
System.out.println("\nSuccessfully saved 32128 bytes");

输出:

Generating report...

[========      24.2 %             ]

[============  45.5 %             ]

[============  78.8 % =====       ]

[============  87.9 % ========    ]

[============ 100.0 % ============]

Successfully saved 32128 bytes

2
此代码以步骤显示百分比,每个步骤为3.33%。如果您想显示作业完成的百分比,请将“String formattedPercent = String.format(”%5.1f%%“,(100 * currentProgressBarIndex)/(double)progressBarLength);”替换为“String formattedPercent = String.format(”%7.3f%%“,(100 * currentValue)/(double)maxValue);”。 - izogfif

3

技术并不需要很复杂。

图片描述

public class Demo {
  private static final StringBuilder sb = new StringBuilder();

  public static void main (String[] args) throws java.lang.Exception
  {
    for (int i = 0 ; i <= 100 ; i++) {
      sb.setLength(0);
      for (int j = 0 ; j < i; j++) {
        sb.append("#");
      }
      Thread.sleep(100);
      System.out.print("[" + String.format("%-100s", sb.toString()) + "] " +  i + "%");
      System.out.print("\r");
    }
  }
}


3
这是上述内容的修改版:
private static boolean loading = true;
private static synchronized void loading(String msg) throws IOException, InterruptedException {
    System.out.println(msg);
    Thread th = new Thread() {
        @Override
        public void run() {
            try {
                System.out.write("\r|".getBytes());
                while(loading) {
                    System.out.write("-".getBytes());
                    Thread.sleep(500);
                }
                System.out.write("| Done \r\n".getBytes());
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };
    th.start();
}

...并在主函数中:

loading("Calculating ...");

2

我最近也遇到了同样的问题,你可以查看我的代码: 我将其设置为一个5%的#,稍后您可以进行修改。

public static void main (String[] args) throws java.lang.Exception
{
    int i = 0;
    while(i < 21) {
        System.out.print("[");
        for (int j=0;j<i;j++) {
            System.out.print("#");
        }

        for (int j=0;j<20-i;j++) {
            System.out.print(" ");
        }

        System.out.print("] "+  i*5 + "%");
        if(i<20) {
            System.out.print("\r");
            Thread.sleep(300);
        }
        i++;
    }
    System.out.println();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接