Java代码中调用shell和python脚本有多种实现方式,通用方式是使用java.lang中的Runtime类新开进程,调用python脚本的一个例子如下(shell同理):
public String python(String pythonPath, String[] params) {
File file = new File(pythonPath);
if (!file.exists()){
return "python脚本不存在!";
}
String[] command = Arrays.copyOf(new String[]{"python", pythonPath}, params.length + 2);
System.arraycopy(params, 0, command, 2, params.length);
List res = new ArrayList<>();
try {
Process process = Runtime.getRuntime().exec(command, null, null);
process.waitFor();
Scanner scanner = new Scanner(process.getInputStream());
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
res.add(line);
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return "success";
}
例子中,参数pythonPath就是python脚本的绝对路径,参数params是脚本的参数数组,command就是执行python命令字符串数组,格式就是python + 脚本 + 参数,构造好command后传入exec()中执行新进程,然后调用waitFor()函数等待进程结束,结束后从进程的输入流中获得脚本的输出结果存储到字符串数组中。
乍一看,上面的代码并没有问题,对于少量的输出结果执行后相当完美,但是当脚本的输出结果大小大于inputStream缓冲区大小时,程序会阻塞在waitFor()函数这里,问题就在于脚本的输出结果是在进程执行完之后才读取,一个好的解决办法就是新开一个清理线程来不断清空缓冲区,也就是输出和读取同时进行,代码如下:
public String python(String pythonPath, String[] params) {
File file = new File(pythonPath);
if (!file.exists()){
return "python脚本不存在!";
}
String[] command = Arrays.copyOf(new String[]{"python", pythonPath}, params.length + 2);
System.arraycopy(params, 0, command, 2, params.length);
List res = new ArrayList<>();
try {
Process process = Runtime.getRuntime().exec(command, null, null);
ClearThread ct = new ClearThread(process);
ct.start();
process.waitFor();
Thread.sleep(1000);
ct.setEnd(true);
res = ct.getRes();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return "success";
}
class ClearThread extends Thread {
Process process;
boolean end;
List res;
public ClearThread(Process process) {
this.process = process;
end = false;
res = new ArrayList<>();
}
@Override
public void run() {
if (process == null) {
return;
}
Scanner scanner = new Scanner(process.getInputStream());
while (process != null && !end) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
res.add(line);
}
}
}
public void setEnd(boolean end) {
this.end = end;
}
public List getRes() {
return res;
}
}
其中,在脚本执行执行完后调用sleep()让主线程睡眠一秒,否则会导致清理线程可能会还没拿到缓冲区数据就被end标识符结束了(可以用小数据试验一下)。