有了前两篇博文的铺垫,这一篇,进入“阶段三”,即在hadoop中调用.so动态库,在动态库中读取文件。
其实苦头都在前面吃了,这部分相对很顺利。
首先,在动态库.so的java wrapper中增加一个函数接口Init,用来load词典文件:
package FakeSegmentForJni; /** * * This class is for verifying the jni technology. It's a fake segmenter. * The only user is its function interface. * */ public class FakeSegmentForJni { public static native boolean Init (String file); public static native String SegmentALine (String line); static { System.loadLibrary("FakeSegmentForJni"); } }
接下来,同《【java学习】Jni在hadoop上的使用(上)》中介绍的过程一样,用javac命令生成FakeSegmentForJni.class文件,用javah命令生成c++头文件FakeSegmentForJni_FakeSegmentForJni.h。用javah的时候,要注意路径问题。FakeSegmentForJni_FakeSegmentForJni.h看起来是这个样子的:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class FakeSegmentForJni_FakeSegmentForJni */ #ifndef _Included_FakeSegmentForJni_FakeSegmentForJni #define _Included_FakeSegmentForJni_FakeSegmentForJni #ifdef __cplusplus extern "C" { #endif /* * Class: FakeSegmentForJni_FakeSegmentForJni * Method: Init * Signature: (Ljava/lang/String;)Z */ JNIEXPORT jboolean JNICALL Java_FakeSegmentForJni_FakeSegmentForJni_Init (JNIEnv *, jclass, jstring); /* * 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 #endif
其实也可以自己来写。
第二,编写FakeSegmentForJni_FakeSegmentForJni.cpp文件内容,实现相关功能,如下:
#include <jni.h> #include <stdio.h> #include <string.h> #include <string> #include <vector> #include <fstream> #include <iostream> #include "FakeSegmentForJni_FakeSegmentForJni.h" using namespace std; // the global varable for lexicon vector <string> WordVec; /* * Class: FakeSegmentForJni_FakeSegmentForJni * * Method: Init * * Signature: (Ljava/lang/String;)Ljava/lang/String; * */ JNIEXPORT jboolean JNICALL Java_FakeSegmentForJni_FakeSegmentForJni_Init (JNIEnv *env, jclass obj, jstring line) { const char *pFileName = NULL; pFileName = env->GetStringUTFChars (line, false); if (pFileName == NULL) return false; ifstream in (pFileName); if (!in) { cerr << "Can not open the file of " << pFileName << endl; return false; } string sWord; while (getline (in, sWord)) { WordVec.push_back(sWord); } return true; } /* * 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]; buf[0] = 0; const char *str = NULL; str = env->GetStringUTFChars(line, false); if (str == NULL) return NULL; strcpy (buf, str); if (!WordVec.empty()) strcat (buf, WordVec.at(0).c_str()); // strcat (buf, "--copy that\n"); env->ReleaseStringUTFChars(line, str); return env->NewStringUTF(buf); }
功能很简单,就是在Init函数中打开一个文件,将文件中的每一行存储在全局变量WordVec中;然后,在SegmentALine函数中,将输入字符串和WordVec中的第一个元素相连接,再输出。用g++将.cpp文件编译成.so文件,命令如下:
g++ -I/System/Library/Frameworks/JavaVM.framework/Versions/A/Headers FakeSegmentForJni_FakeSegmentForJni.cpp -fPIC -shared -o libFakeSegmentForJni.so
第三步,写hadoop程序。
map函数:
public static class MapTestJni extends Mapper<Writable, Text, Text, Text> { protected String s; protected void setup(Context context) throws IOException, InterruptedException { FakeSegmentForJni.Init("Lex.txt"); s = FakeSegmentForJni.SegmentALine("jni-value"); } protected void map(Writable key, Text value, Context context) throws IOException, InterruptedException { // the format of input value is: // mcid totaltimes item1 item2(itemkey=itemvalue) context.write(new Text("key"), new Text(s.toString())); } }
在setup函数中,我们调用了FakeSegmentForJni.Init函数,来load文件Lex.txt中的内容。可以看到,相对路径就是本地当前路径。在下文分发过程中,会将Lex.txt文件分发到与jar文件相同的本地路径下。在map函数中,输出s的内容。reduce函数、控制函数和main函数与《【java学习】Jni在hadoop上的使用(中)》的一样,这里就不重复粘贴了。
第四步,在命令行中用“-files”参数将.so文件和Lex.txt文件分发到tasknode上,命令如下:
hadoop jar /xxx/TestFakeSegmentForJniHadoop.jar -files /xxx/TestJni/libFakeSegmentForJni.so,/xxx/TestJni/Lex.txt FakeSegmentForJni.TestFakeSegmentForJni /input/xxx.txt /outputJNI
多个文件用逗号间隔。
最后,检查运行结果,结果正确。