java调用python脚本(传长参数)
背景
项目中需要使用java去调用python脚本并且需要传递一个包含图像信息的byte数组,这个数组长度为4,194,304.
java调用python无外乎两种方式
-
jpython
jpython既可以在java中写python代码,也可以使用其提供的 PythonInterpreter 类来调用python脚本,但是无论是直接写python代码还是调用python脚本, Jython 都是 将Python语言实现为Java虚拟机(JVM)上的解释器的项目 ,而Jython 到目前为止只能支持python2 的语法和语义,并不支持Python 3语法。
-
Runtime.getRuntime().exec(cmdArr) 或者 ProcessBuilder 模拟命令行调用
这两种方式都是通过模拟命令行命令来调用python脚本的,一般情况下,我们可以把要传递的参数直接写在命令行中就可以了.但是我此次要传递的参数很长,写在命令行中执行时会报超长的异常.
解决方案
jpython不能兼容python3是硬伤,因此只能选择模拟命令行的方式调用,传参方式则使用获取进程的标准输出流,将参数写进流里进行传递(命令行调用python脚本会新起一个进程,即要使用这个进程的标准输出流).具体实现代码如下:
java部分代码
public static ComputeResponse computeTest(ComputeRequest computeRequest) {
JSONObject requestJson = new JSONObject();
requestJson.put("rgbWidth", computeRequest.getRgbWidth());
requestJson.put("rgbHeight", computeRequest.getRgbHeight());
requestJson.put("rgbBands", computeRequest.getRgbBands());
requestJson.put("rgbData", Arrays.toString(computeRequest.getRgbData()));
requestJson.put("specWidth", computeRequest.getSpecWidth());
requestJson.put("specHeight", computeRequest.getSpecHeight());
requestJson.put("specBands", computeRequest.getSpecBands());
requestJson.put("spectralData", Arrays.toString(computeRequest.getSpectralData());
ComputeResponse response = ComputeResponse.builder()
.success(false)
.build();
BufferedReader reader = null;
try {
// String[] cmdArr = new String[]{"python3", tobacEnginePath, requestJson.toJSONString()};
String[] cmdArr = new String[]{"python3", "D:\\workspace\\CompanyProjects\\tobacco-impurity-detection\\script\\Demo.py"};
boolean execResFlag = false;
StringBuilder sb = null;
int tryCount = 1;
while (tryCount <= 5) {
Process process = Runtime.getRuntime().exec(cmdArr);
OutputStream outputStream = process.getOutputStream();
outputStream.write(requestJson.toJSONString().getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
sb = new StringBuilder();
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
reader.close();
// 0表示程序正常终止
int execCode = process.waitFor();
if (Objects.equals(0, execCode)) {
log.info("recive data:{}", sb);
break;
}
log.error("the [{}] time try call python script fail, exec code: {}", tryCount, execCode);
tryCount++;
ThreadUtils.sleep(20L);
}
} catch (Exception e) {
log.error("Call python script fail: ", e);
response.setSuccess(false);
} finally {
StreamUtils.close(reader);
}
return response;
}
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public static class ComputeRequest {
private Integer rgbWidth;
private Integer rgbHeight;
private Integer rgbBands;
private byte[] rgbData;
private Integer specWidth = 1;
private Integer specHeight = 1;
private Integer specBands = 1;
private float[] spectralData = new float[]{1,1,1};
}
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public static class ComputeResponse {
private Boolean success;
private String mess_isExist;
private String mess_info;
private String status;
}
传参时并不是在String[] cmdArr
中把参数也拼上使用命令行传参,而是先获取了进程的标准输出流OutputStream outputStream = process.getOutputStream();
然后将参数写入到标准输出流中outputStream.write(requestJson.toJSONString().getBytes(StandardCharsets.*UTF_8*));
,这样python脚本那边可以通过input_data = sys.stdin.read()
的方式获取到参数.
注意
-
写入流后要关闭
outputStream
,不关闭的话python脚本那边会一直等待关闭命令而不往下执行. -
在给
requestJson
赋值byte[]时,把byte[]转成String格式,这点很重要,这是因为当在 Python 中接收到字节数组参数时,默认情况下,它们以字节的字符形式表示 ,而我们需要在python中也获取图像的字节数组才能对图像进行下一步操作,总而言之就是保证了我java传递的是什么,python就接收到的是什么
python代码
import json
import sys
import numpy as np
from matplotlib import pyplot as plt
if __name__ == '__main__':
input_data = sys.stdin.read()
# params = demjson3.decode(input_data)
params = json.loads(input_data)
rgbData_array = json.loads(params['rgbData'])
temp1 = np.array(rgbData_array)
rgbdata = np.reshape(temp1, (1024, 4096, 1))
plt.imshow(rgbdata)
plt.savefig('E:/temp/rgb.png')
print(params['rgbData'])
注意
-
将str解析成json时使用官方库的
json.loads
,不要使用demjson3.decode
,效率天上地下的差距 -
java传参我们把数组转成了String的形式,python这边我们要用
json.loads
方法把str再转成array
这样,我们就可以在java调用python脚本的时候传递很长的参数了.当然解决方案不止此一种,比如我们可以保存临时文件,或者分批传参.但总体来说此种方案最优