Jni在Hadoop上的使用(3)

有了前两篇博文的铺垫,这一篇,进入“阶段三”,即在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

多个文件用逗号间隔。

最后,检查运行结果,结果正确。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/54b0de2cbcb60fb830d0765872a93cbc.html