Java - 持续使用 ProcessBuilder 的 Input 和 OutputStream
Java - Use Input and OutputStream of ProcessBuilder continuously
我想在提取一些数据时使用外部工具(循环遍历行)。
为此,我首先使用 Runtime.getRuntime().exec() 来执行它。
但是后来我的提取速度变得非常慢。所以我正在寻找在每个循环实例中执行外部工具的可能性,使用 shell 的相同实例。
我发现我应该使用 ProcessBuilder。但它还没有工作。
这是我测试执行的代码(已经从论坛中的答案输入):
public class ExecuteShell {
ProcessBuilder builder;
Process process = null;
BufferedWriter process_stdin;
BufferedReader reader, errReader;
public ExecuteShell() {
String command;
command = getShellCommandForOperatingSystem();
if(command.equals("")) {
return; //Fehler! No error handling yet
}
//init shell
builder = new ProcessBuilder( command);
builder.redirectErrorStream(true);
try {
process = builder.start();
} catch (IOException e) {
System.out.println(e);
}
//get stdout of shell
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
errReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
//get stdin of shell
process_stdin = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
System.out.println("ExecuteShell: Constructor successfully finished");
}
public String executeCommand(String commands) {
StringBuffer output;
String line;
try {
//single execution
process_stdin.write(commands);
process_stdin.newLine();
process_stdin.flush();
} catch (IOException e) {
System.out.println(e);
}
output = new StringBuffer();
line = "";
try {
if (!reader.ready()) {
output.append("Reader empty \n");
return output.toString();
}
while ((line = reader.readLine())!= null) {
output.append(line + "\n");
return output.toString();
}
if (!reader.ready()) {
output.append("errReader empty \n");
return output.toString();
}
while ((line = errReader.readLine())!= null) {
output.append(line + "\n");
}
} catch (Exception e) {
System.out.println("ExecuteShell: error in executeShell2File");
e.printStackTrace();
return "";
}
return output.toString();
}
public int close() {
// finally close the shell by execution exit command
try {
process_stdin.write("exit");
process_stdin.newLine();
process_stdin.flush();
}
catch (IOException e) {
System.out.println(e);
return 1;
}
return 0;
}
private static String getShellCommandForOperatingSystem() {
Properties prop = System.getProperties( );
String os = prop.getProperty( "os.name" );
if ( os.startsWith("Windows") ) {
//System.out.println("WINDOWS!");
return "C:/cygwin64/bin/bash";
} else if (os.startsWith("Linux") ) {
//System.out.println("Linux!");
return"/bin/sh";
}
return "";
}
}
我想在另一个 Class 中这样调用它 Testclass:
public class TestExec{
public static void main(String[] args) {
String result = "";
ExecuteShell es = new ExecuteShell();
for (int i=0; i<5; i++) {
// do something
result = es.executeCommand("date"); //execute some command
System.out.println("result:\n" + result); //do something with result
// do something
}
es.close();
}
}
我的问题是,输出流总是空的:
ExecuteShell: Constructor successfully finished
result:
Reader empty
result:
Reader empty
result:
Reader empty
result:
Reader empty
result:
Reader empty
我在这里阅读了主题:Java Process with Input/Output Stream
但是代码片段不足以让我继续,我遗漏了一些东西。我并没有真正使用过不同的线程。而且我不确定 if/how 扫描仪是否对我有任何帮助。非常感谢您的帮助。
最终,我的目标是重复调用外部命令并使其快速。
编辑:
我改变了循环,所以 es.close() 在外面。我想补充一点,我不希望循环内只有这个。
编辑:
时间的问题是,我调用的命令导致了错误。当命令没有报错时,时间是可以接受的。
感谢您的回答
您可能遇到了竞争条件:将命令写入 shell 后,您的 Java 程序继续 运行,并且几乎立即调用 reader.ready()
。您要执行的命令可能尚未输出任何内容,因此 reader 没有可用数据。另一种解释是该命令不会向 stdout 写入任何内容,而只会写入 stderr(或 shell,也许它未能启动该命令?)。然而,您实际上并没有从 stderr 读取。
要正确处理输出和错误流,您不能检查 reader.ready()
,但需要在循环中调用 readLine()
(等待数据可用)。使用您的代码,即使程序会到达那一点,您也只能从输出中读取一行。如果程序输出多行,则此数据将被解释为下一条命令的输出。典型的解决方案是循环读取直到 readLine()
returns null
,但这在这里不起作用,因为这意味着您的程序将在此循环中等待直到 shell终止(这永远不会发生,所以它会无限期地挂起)。
如果您不确切知道每个命令将写入 stdout 和 stderr 的行数,那么解决这个问题几乎是不可能的。
但是,您使用 shell 并向其发送命令的复杂方法可能完全没有必要。从 Java 程序中启动命令和从 shell 程序中启动命令一样快,而且更容易编写。同样,Runtime.exec()
和ProcessBuilder
之间没有性能差异(前者只是调用后者),如果你需要它的高级功能,你只需要ProcessBuilder
。
如果你在调用外部程序时遇到性能问题,你应该找出它们的确切位置并尝试解决它们,但不是用这种方法。例如,通常启动一个线程来读取输出和错误流(如果您不启动单独的线程并且命令产生大量输出,一切都可能挂起)。这可能会很慢,因此您可以使用线程池来避免重复生成进程。
我想在提取一些数据时使用外部工具(循环遍历行)。 为此,我首先使用 Runtime.getRuntime().exec() 来执行它。 但是后来我的提取速度变得非常慢。所以我正在寻找在每个循环实例中执行外部工具的可能性,使用 shell 的相同实例。
我发现我应该使用 ProcessBuilder。但它还没有工作。
这是我测试执行的代码(已经从论坛中的答案输入):
public class ExecuteShell {
ProcessBuilder builder;
Process process = null;
BufferedWriter process_stdin;
BufferedReader reader, errReader;
public ExecuteShell() {
String command;
command = getShellCommandForOperatingSystem();
if(command.equals("")) {
return; //Fehler! No error handling yet
}
//init shell
builder = new ProcessBuilder( command);
builder.redirectErrorStream(true);
try {
process = builder.start();
} catch (IOException e) {
System.out.println(e);
}
//get stdout of shell
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
errReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
//get stdin of shell
process_stdin = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
System.out.println("ExecuteShell: Constructor successfully finished");
}
public String executeCommand(String commands) {
StringBuffer output;
String line;
try {
//single execution
process_stdin.write(commands);
process_stdin.newLine();
process_stdin.flush();
} catch (IOException e) {
System.out.println(e);
}
output = new StringBuffer();
line = "";
try {
if (!reader.ready()) {
output.append("Reader empty \n");
return output.toString();
}
while ((line = reader.readLine())!= null) {
output.append(line + "\n");
return output.toString();
}
if (!reader.ready()) {
output.append("errReader empty \n");
return output.toString();
}
while ((line = errReader.readLine())!= null) {
output.append(line + "\n");
}
} catch (Exception e) {
System.out.println("ExecuteShell: error in executeShell2File");
e.printStackTrace();
return "";
}
return output.toString();
}
public int close() {
// finally close the shell by execution exit command
try {
process_stdin.write("exit");
process_stdin.newLine();
process_stdin.flush();
}
catch (IOException e) {
System.out.println(e);
return 1;
}
return 0;
}
private static String getShellCommandForOperatingSystem() {
Properties prop = System.getProperties( );
String os = prop.getProperty( "os.name" );
if ( os.startsWith("Windows") ) {
//System.out.println("WINDOWS!");
return "C:/cygwin64/bin/bash";
} else if (os.startsWith("Linux") ) {
//System.out.println("Linux!");
return"/bin/sh";
}
return "";
}
}
我想在另一个 Class 中这样调用它 Testclass:
public class TestExec{
public static void main(String[] args) {
String result = "";
ExecuteShell es = new ExecuteShell();
for (int i=0; i<5; i++) {
// do something
result = es.executeCommand("date"); //execute some command
System.out.println("result:\n" + result); //do something with result
// do something
}
es.close();
}
}
我的问题是,输出流总是空的:
ExecuteShell: Constructor successfully finished
result:
Reader empty
result:
Reader empty
result:
Reader empty
result:
Reader empty
result:
Reader empty
我在这里阅读了主题:Java Process with Input/Output Stream
但是代码片段不足以让我继续,我遗漏了一些东西。我并没有真正使用过不同的线程。而且我不确定 if/how 扫描仪是否对我有任何帮助。非常感谢您的帮助。
最终,我的目标是重复调用外部命令并使其快速。
编辑: 我改变了循环,所以 es.close() 在外面。我想补充一点,我不希望循环内只有这个。
编辑: 时间的问题是,我调用的命令导致了错误。当命令没有报错时,时间是可以接受的。
感谢您的回答
您可能遇到了竞争条件:将命令写入 shell 后,您的 Java 程序继续 运行,并且几乎立即调用 reader.ready()
。您要执行的命令可能尚未输出任何内容,因此 reader 没有可用数据。另一种解释是该命令不会向 stdout 写入任何内容,而只会写入 stderr(或 shell,也许它未能启动该命令?)。然而,您实际上并没有从 stderr 读取。
要正确处理输出和错误流,您不能检查 reader.ready()
,但需要在循环中调用 readLine()
(等待数据可用)。使用您的代码,即使程序会到达那一点,您也只能从输出中读取一行。如果程序输出多行,则此数据将被解释为下一条命令的输出。典型的解决方案是循环读取直到 readLine()
returns null
,但这在这里不起作用,因为这意味着您的程序将在此循环中等待直到 shell终止(这永远不会发生,所以它会无限期地挂起)。
如果您不确切知道每个命令将写入 stdout 和 stderr 的行数,那么解决这个问题几乎是不可能的。
但是,您使用 shell 并向其发送命令的复杂方法可能完全没有必要。从 Java 程序中启动命令和从 shell 程序中启动命令一样快,而且更容易编写。同样,Runtime.exec()
和ProcessBuilder
之间没有性能差异(前者只是调用后者),如果你需要它的高级功能,你只需要ProcessBuilder
。
如果你在调用外部程序时遇到性能问题,你应该找出它们的确切位置并尝试解决它们,但不是用这种方法。例如,通常启动一个线程来读取输出和错误流(如果您不启动单独的线程并且命令产生大量输出,一切都可能挂起)。这可能会很慢,因此您可以使用线程池来避免重复生成进程。