Jni在Hadoop上的使用(一)

2014-11-24 09:10:21 · 作者: · 浏览: 3

如果只是使用hadoop,用c++ pipes实现hadoop程序,再调用c++实现的分词器(源代码调用或者动态库调用)就很简单,不存在上面的问题。不过,由于Legacy原因(其实就是种种原因),不能放弃java版本的hadoop程序,才会有以上问题。


上网上搜了一下,Java调用c++用的是JNI(java native interface)技术,只是JNI怎么放到hadoop中?而且分词器要读取资源文件(词表),这个文件在hadoop中的路径设定有什么规矩?我就不知道了。


尝试分三阶段进行:


阶段一:在linux跑通一个单机版的JNI程序,即用java调用c++。


阶段二:将上面的程序放到hadoop上跑通。


阶段三:让c++编出来的动态库(so文件)load资源,并在hadoop上跑通。


现在进行阶段一的工作。


1. 写一个Java类,用来包装c++代码的接口。这里面我只是写了示意性代码,毕竟是要尝试么,如下:


package FakeSegmentForJni ;

public class FakeSegmentForJni {
public static native String SegmentALine (String line);

static
{
System.loadLibrary("FakeSegmentForJni");
}
}


这里面声明了静态函数接口,并用了”native“关键字,表示是native函数(非java的、本地函数)。在”static“语句块儿中,用LoadLibrary调用(即将生成的)c++动态库。


2. 用javac命令编译FakeSegmentForJni类,生成.class文件,命令如下:


javac -d ./bin ./src/*.java


3. 在FakeSegmentForJni.class的基础上,用javah命令生成c++函数的头文件,命令如下:


javah -jni -classpath . FakeSegmentForJni.FakeSegmentForJni


其中classpath表示.class文件所在目录,“.”表示当前目录;后面的参数,第一个“FakeSegmentForJni”表示package名称,第二个“FakeSegmentForJni”表示class名称。敲完命令后,就能在当前目录下发现c++函数的头文件FakeSegmentForJni_FakeSegmentForJni.h。打开看一下,主要将java类中的static函数转成了c++接口,内容如下:


/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class FakeSegmentForJni_FakeSegmentForJni */

#ifndef _Included_FakeSegmentForJni_FakeSegmentForJni
#define _Included_FakeSegmentForJni_FakeSegmentForJni
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: FakeSegmentForJni_FakeSegmentForJni
* Method: SegmentALine
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_FakeSegmentForJni_FakeSegmentForJni_SegmentALine
(JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif


文件上第一句“DO NOT EDIT THIS FILE ......”表示这是个自动生成的文件。其时,不必用javah命令来生成这个文件,手写也没问题。不过毕竟自动生成方便,尤其是在接口函数比较多的情况。


4. 费了这么半天事情,就是生成了一个c++头文件,正经事还没干呢。什么是正经事?既然非用c++不可,正经事就是用c++对所需功能的实现。其时用c++的理由是尽量利用现有的、成熟的代码,所以这一步,一般不是功能性开发,而是写个wrapper包装现有的代码——如果是纯功能性开发,那直接用java的了,费这么多事干嘛?!


废话不说了,wrapper的代码如下:


#include
#include
#include
#include "FakeSegmentForJni_FakeSegmentForJni.h"

/* * Class: FakeSegmentForJni_FakeSegmentForJni
* * Method: SegmentALine
* * Signature: (Ljava/lang/String;)Ljava/lang/String;
* */

JNIEXPORT jstring JNICALL Java_FakeSegmentForJni_FakeSegmentForJni_SegmentALine
(JNIEnv *env, jclass obj, jstring line)
{
char buf[128];
const char *str = NULL;
str = env->GetStringUTFChars(line, false);
if (str == NULL)
return NULL;
strcpy (buf, str);
strcat (buf, "--copy that\n");
env->ReleaseStringUTFChars(line, str);
return env->NewStringUTF(buf);
}


5. 在本地环境下编译出c++动态库。为啥要强调“在本地环境”?字面上的意思就是,你在windows下用JNI,就到windows下编译FakeSegmentForJni_FakeSegmentForJni.cpp文件,生成dll;在linux下,就到linux下(32位还是64位自己搞清楚)编译FakeSegmentForJni_FakeSegmentForJni.cpp文件,生成.so文件。为啥非要这样?这就涉及到动态库的加载过程,每个系统都不一样。


编译命令为:


g++ -I/System/Library/Frameworks/JavaVM.framework/Versions/A/Headers FakeSegmentForJni_FakeSegmentForJni.cpp -fPIC -shared -o FakeSegmentForJni.so


命令有点长,不过意思很容易。“-I”表示要包含的头文件。正常来讲,系统路径都已经为g++设置好了。不过jni.h是java的头文件,不是c++的,g++找不到,只好在编译的时候告诉编译器。我这里用的是mac os 10.8.2的hadoop伪分布式,所以头文件是在framework里的,路径“/System/Library/Frameworks/JavaVM.framework/Versions/A/Headers”看起来有点怪怪的。”-shared“表示输