我花了几天时间寻找一种实时读取控制台应用程序输出的方法。我遇到的每一种方式几乎都与其他方式完全相同,但是似乎不起作用。每当我运行代码时,我的控制台命令都不会在几分钟内输出任何文本,这使我认为它实际上是在执行结束时而不是在执行期间输出数据。但是,现在我知道存在某种瓶颈,我不认为这与我的代码有关。
很难交出一个更简单的可重现示例,因为我认为这会使理解变得更加困难,所以请原谅。我什至不确定这个问题是否可以重现,因为这似乎是环境问题。
我正在运行Windows 7(我知道,我很酷)。
我正在使用IntelliJ IDEA IDE(它几乎超过了运行Windows 7的感觉)。
这是java --version
的输出:
Java 11.0.3 2019-04-16 LTS
Java(TM)SE运行时环境18.9(内部版本11.0.3 + 12-LTS)
Java HotSpot(TM)64位服务器VM 18.9(内部版本11.0.3 + 12-LTS,混合模式)
为说明问题,我有以下代码:
public class Main {
private static Timer timer;
public static void main(String[] args) throws IOException {
java.lang.String[] commands = {"cmd", "/c", "sfc", "/scannow"};
ProcessBuilder pBuilder = new ProcessBuilder(commands);
pBuilder.redirectErrorStream();
timer = new Timer();
try {
dbg("Starting process...");
Process p = pBuilder.start();
dbg("Process started.");
InputStream in = p.getInputStream();
final Scanner scanner = new Scanner(in);
new Thread(() -> {
dbg("Thread started.");
int i = 1;
while (scanner.hasNextLine()) {
String nextLine = scanner.nextLine();
dbg("Line " + i + " - " + nextLine);
i++;
}
scanner.close();
dbg("Closing scanner.");
}).start();
dbg("Waiting for exit status result...");
int exitStatus = p.waitFor();
dbg("exit status: " + exitStatus);
p.destroy();
} catch (NullPointerException | InterruptedException | IOException e) {
e.printStackTrace();
}
}
private static void dbg(String messageToAppend) {
System.out.println(timer.getTimePassedAsStr(messageToAppend));
}
}
我制作了一个简单的计时器类来帮助我调试代码,因为开始运行花费的时间比我预期的要长得多。它工作正常,所以我不会发布代码。但是,这是我得到的运行结果:
[000:00.000] Starting process...
[000:00.149] Process started.
[000:00.150] Input stream created.
[000:00.204] Scanner created.
[000:00.205] Waiting for exit status result...
[000:00.205] Thread started.
[005:51.762] Line 1 -
[005:51.763] Line 2 -
[005:51.763] Line 3 - Beginning system scan. This process will take some time.
[005:51.763] Line 4 -
[005:51.763] Line 5 -
[005:51.764] Line 6 -
[005:51.764] Line 7 - Beginning verification phase of system scan.
[005:51.764] Line 8 -
[012:42.484] Line 9 - Verification 100% complete.
[012:42.485] Line 10 - Windows Resource Protection found corrupt files but was unable to fix some of them.
[012:42.486] Line 11 -
[012:42.486] Line 12 - Details are included in the CBS.Log windir\Logs\CBS\CBS.log. For example
[012:42.487] Line 13 -
[012:42.487] Line 14 - C:\Windows\Logs\CBS\CBS.log
[012:42.487] Line 15 -
[012:42.488] Closing scanner.
[012:42.491] exit status: 0
通常,当我直接从控制台运行sfc /scannow
时,它几乎立即启动,但是当我从Java应用程序中运行它时,几乎有6分钟的延迟才能得到任何输出。
我也试图像这样通过CMD解释器执行它:
java.lang.String[] commands = {"cmd", "/c", "sfc", "/scannow"};
但是我得到相同的结果。我还尝试过将其作为单线程运行,如下所示:
public static void runSfc() throws IOException {
timer = new Timer();
Runtime rt = Runtime.getRuntime();
String[] commands = {"sfc", "/scannow"};
dbg("Running command...");
Process proc = rt.exec(commands);
BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
String s;
int i = 1;
dbg("Here is the standard output of the command:");
while ((s = stdInput.readLine()) != null) {
dbg("Line " + i + " - " + s);
i++;
}
i = 1;
dbg("Here is the standard error of the command (if any):");
while ((s = stdError.readLine()) != null) {
dbg("Line " + i + " - " + s);
i++;
}
}
似乎随后的每个运行(无论是单线程还是多线程)花费的时间大致相同,甚至更长,甚至莫名其妙地随后又缩短了。最近一次运行是在控制台窗口中看到第一行输出之前的15分钟。
该应用需要以管理员身份运行吗?
我是从IDE运行代码,还是从命令行编译并运行代码都没关系。
我尝试重新安装Java-没什么区别。
我尝试使用版本吗?相反-没有区别。
奇怪的是,当扫描因为已经在其他地方运行而无法运行时,它在大约25秒(超过5分钟的时间)后开始其初始输出。在线程内运行时也是如此。
[000:00.000] Running command...
[000:00.203] Here is the standard output of the command:
[000:25.508] Line 1 -
[000:25.509] Line 2 -
[000:25.509] Line 3 - Beginning system scan. This process will take some time.
[000:25.509] Line 4 -
[000:25.509] Line 5 -
[000:25.509] Line 6 - Another servicing or repair operation is currently running.
[000:25.509] Line 7 -
[000:25.509] Line 8 - Wait for this to finish and run sfc again.
[000:25.509] Line 9 -
[000:25.510] Here is the standard error of the command (if any):
[000:25.510] End of execution
问题1:为什么从命令行运行该程序时,输出任何东西要花5分钟以上的时间,几乎立即产生输出? Windows机器上的其他任何人都会遇到这种情况吗?
问题2:我想在扫描进行过程中收到进度更新。我还没有意识到,由于进度更新之前带有\ r且在过程达到100%之前不会以\ n结尾,因此在扫描完成之前,我看不到任何更新消息,因为它从未实际注册为已接收到一行输出。显然,我需要读取字节而不是行。没有人知道一种阅读这种文本的方式(以\ r开头,直到过程结束时才以\ n结尾)而无需我重新发明轮子的方法吗?
编辑: 直接通过CLI执行时的平均平均执行时间 13m 53s(5次跑步)
通过Java应用执行时的平均执行时间 12m 32s(5次跑步)
因此,看起来Java应用程序内的执行时间与直接在控制台中执行的时间大致相同,并且执行时间一点也不慢。只是需要一段时间才能显示输出的第一行。
我只能得出结论,这确实是@Andreas建议的缓冲区刷新问题。就目前而言,我认为没有办法解决这个问题。