1 为什么我们要Java调用Python或R
Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程 。Java具有简单性、面向对象、分布式、健壮性、安全性、平台独立与可移植性、多线程、动态性等特点。Java可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序等;Python和R在数据科学中有较高的地位,能够快速的实现特征工程、数据挖掘的建模。因此在生产中可能需要上线一些数据挖掘模型时,此时软件工程师(往往不懂数据科学)使用的工具Java和数据分析师的Python或R会有交集。
模型的线上化部署的方式有很多,比如我们可以把训练的模型转预测模型标记语言(PMML),这样Java就可以直接调用PMML;也可以做成REST API,Java通过API接口与模型进行交互,以上这些R与Python都有成熟的解决办法;最直接的办法莫过于代码与代码的对接(code2code),我们可以通过Java直接调用开发好的R模型或Python模型,完成训练,评价和预测的目的,下面我们会依次展示一个Java调用Python代码的例子和Java调用R的例子。
2.Java调用Python举栗
首先我们知道Python和Java是可以相互调用的,而本节仅举一个Java调用Python的栗子,Java调用Python的方式有很多,比如Jython(不过我不推荐大家使用,Jython版本更新缓慢,很多Python模块并没有包含,在调用的过程中会出现各种问题),我推荐大家使用命令行的方式调用Python代码或脚本,其核心方法为:
Runtime.getRuntime().exec(args1);
注意这种方式只能接收到python里print的数据。所以如果你需要返回值,可以把返回值打印出来由Java接收就OK了,请后退,我要上代码了:
首先我们要有一个Python脚本文件,比如:
import sys
import pandas as pd
import numpy as np
import sklearn
import xgboost
import lightgbm
import tensorflow as tf
import keras
def my_test(str1,str2,str3,str4):
return "Python函数运行:java调Python测试:"+str1+str2+str3+str4
if __name__=="__main__":
print("脚本名:", sys.argv[0])
my_arg = []
for i in range(0, len(sys.argv)):
my_arg.append(sys.argv[i])
print("Java传入的参数长度为:"+str(len(my_arg)))
result = my_test(my_arg[1],my_arg[2],my_arg[3],my_arg[4])
print(result)
其次我们构造一个J2py类用来调用上述Python脚本,并且实现Java数据与Python的交互(动态传参的过程):
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
/**
* @title J2py.java
* @author 作者:XuJing
* @date 创建时间:2018年7月13日下午2:39:05
* @version 1.0.0
* @parameter 参数及其意义:
* @return 返回值:
*/
public class J2py {
public static void main(String[] args) {
// 需传入的参数
String a = "你好", b = "123", c = "徐静", d = "qingdao";
System.out.println("Java中动态参数已经初始化,准备传参");
// 设置命令行传入参数
String[] args1 = new String[] { "python","java\\03_project\\J2py\\src\\my_model.py", a,b, c, d };
//Java数据a,b,c,d传入Python
Process pr;
try {
pr = Runtime.getRuntime().exec(args1); //最核心的函数
BufferedReader in = new BufferedReader(new InputStreamReader(pr.getInputStream(), "gbk"));
String line;
List<String> lines = new ArrayList<String>();
System.out.println("-----------------------------------------------");
while ((line = in.readLine()) != null) {
System.out.println(line);
lines.add(line); //把Python的print值保存了下来
}
System.out.println("-------------------------------------------------");
in.close();
pr.waitFor();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Java调Python结束");
}
}
在Eclipse下运行的结果如下:
Java中动态参数已经初始化,准备传参
-----------------------------------------------
脚本名: java\03_project\J2py\src\my_model.py
Java传入的参数长度为:5
Python函数运行:java调Python测试:你好123徐静qingdao
-------------------------------------------------
Java调Python结束
OK,成功了,虚线中间的部分就是我Python脚本实现的功能,并且我们实现了Java数据与Python代码的参数传递。注意一点我在Python脚本中导入了部分我们常用到的模块,这种方式Java并没有报错,也就意味着我们均可以通过Java调用这些模块构造的模型,简直是太棒了。
附:我的测试环境是Win7,Python3.6.4,JDK10.0.1,这里需要说明的是我们也可以调用Python的虚拟环境,只是把Python的代码稍作修改就可以实现。
3.Java调用R举栗
R与Java同样可以进行相互调用,实际中很多R包都是由Java完成的,本节重点依然放在Java调用R代码或脚本中,要注意的是Java调用R的方式也有很多,我们本节主要举一个Java通过Rserve调用R代码或脚本的例子,同时实现Python数据与R脚本的交互。翠花,上代码:
首先我们要有一段R脚本,在现实中可能是你用R开发好的模型,等待上线:
library(tidyverse)
library(ggplot2)
library(mlr)
library(xgboost)
library(tensorflow)
library(keras)
getSum <- function(x,y){
m <- x + y
print("成功执行了该R函数")
return(m)
}
其次,我们构造一个J2r的Java类,用来调用上面的R脚本,并且实现数据交互和在Java中自动开启Rserve:
import org.rosuda.REngine.REXP;
import org.rosuda.REngine.REXPMismatchException;
import org.rosuda.REngine.Rserve.RConnection;
import org.rosuda.REngine.Rserve.RserveException;
/**
* @title J2r2.java
* @author 作者:XuJing
* @date 创建时间:2018年7月13日下午5:31:12
* @version 1.0.0
* @parameter 参数及其意义:
* @return 返回值:
*/
public class J2r2 {
public static void main(String[] args) {
System.out.println(StartRserve.checkLocalRserve());
System.out.println("准备开始Java调用R");
System.out.println("-----------------------------------------------");
RConnection rConnection = null;
try {
rConnection = new RConnection();
rConnection.eval("source('C:/test.R')");
} catch (RserveException e) {
e.printStackTrace();
} // 文件名不能带中文,否则报错:eval failed, request status: error code: 127
int a = 2;
int b = 3;
int c = 4;
int sum = 0;
try {
sum = rConnection.eval("getSum(" + a + "," + b + ")").asInteger();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("the sum = " + sum);
rConnection.close();
// 调用R代码
System.out.println("调用R代码");
RConnection rc = null;
try {
rc = new RConnection();
} catch (RserveException e) {
e.printStackTrace();
}
REXP x = null;
try {
x = rc.eval("library(xgboost);R.version.string");
} catch (RserveException e) {
e.printStackTrace();
}
try {
System.out.println(x.asString());
} catch (REXPMismatchException e) {
e.printStackTrace();
}
rc.close();
System.out.println("-----------------------------------------------");
System.out.println("回到Java");
}
}
在Eclipse下运行的结果如下:
准备开始Java调用R
-----------------------------------------------
the sum = 5
调用R代码
R version 3.4.0 (2017-04-21)
-----------------------------------------------
回到Java
OK成功。java同时可以调用第三方的R包,执行R脚本传参,并把数据返回给Java,这里要注意的是需要导入Rengine.jar和Rserve.jar两个压缩包,并且如果要实现Java中自动开启Rserve需要在Rserve包中找到Rserve.java源文件进行调用。
附:我的测试环境是Win7,R3.4.0,JDK10.0.1, 特别注意:不建议在Windows系统使用Rserve.
4.小结
通过以上两种简单方式我们就可以非常容易的通过Java调用R和Python封装的数据挖掘模型。实际上这是一种最简单直接的模型线上化部署的办法,在生产中我们要基于硬件和系统要求选择合理的模型线上化部署的办法。同时也可以直接使用Java或C等底层的语言开发自己的模型,减少不同代码间相互调用,提高模型的生产效率和运行速度。
原文发布时间为:2018-07-17
本文作者: 徐静
本文来自云栖社区合作伙伴“Python爱好者社区”,了解相关信息可以关注“Python爱好者社区”