[hadoop@Master bin]$ ll
total 12
drwxrwxr-x 2 hadoop hadoop 4096 Nov 28 10:48 FakeSegmentForJni
-rw-rw-r-- 1 hadoop hadoop 700 Nov 28 11:01 FakeSegmentForJni_FakeSegmentForJni.cpp
-rw-rw-r-- 1 hadoop hadoop 562 Nov 28 10:52 FakeSegmentForJni_FakeSegmentForJni.h
5. 在本地环境下编译出c++动态库。为啥要强调“在本地环境”?字面上的意思就是,你在windows下用JNI,就到windows下编译FakeSegmentForJni_FakeSegmentForJni.cpp文件,生成dll;在linux下,就到linux下(32位还是64位自己搞清楚)编译FakeSegmentForJni_FakeSegmentForJni.cpp文件,生成.so文件。为啥非要这样?这就涉及到动态库的加载过程,每个系统都不一样。 [hadoop@Master bin]$ g++ -I/usr/java/jdk1.6.0_31/include -I/usr/java/jdk1.6.0_31/include/linux FakeSegmentForJni_FakeSegmentForJni.cpp -fPIC -shared -o libFakeSegmentForJni.so
命令有点长,不过意思很容易。“-I”表示要包含的头文件。正常来讲,系统路径都已经为g++设置好了。不过jni.h是java的头文件,不是c++的,g++找不到,只好在编译的时候告诉编译器。
我这里jni.h是在/usr/java/jdk1.6.0_31/include目录下,还要包含/usr/java/jdk1.6.0_31/include/linux,因为jni.h文件也调用也这里面的东西,不然会有错误。
还有库文件的文件名前必须要有lib,不然即使指定了正确的库文件路径也会找不到
这样,就有了libFakeSegmentForJni.so文件 [hadoop@Master bin]$ ll
total 20
drwxrwxr-x 2 hadoop hadoop 4096 Nov 28 10:48 FakeSegmentForJni
-rw-rw-r-- 1 hadoop hadoop 700 Nov 28 11:01 FakeSegmentForJni_FakeSegmentForJni.cpp
-rw-rw-r-- 1 hadoop hadoop 562 Nov 28 10:52 FakeSegmentForJni_FakeSegmentForJni.h
-rwxrwxr-x 1 hadoop hadoop 7703 Nov 28 11:03 libFakeSegmentForJni.so
6.本地java程序调用FakeSegmentForJni.so。回到src目录下,创建TestFakeSegmentForJni.java
package FakeSegmentForJni;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
/**
* This class is for verifying the jni technology.
* It call the function defined in FakeSegmentForJni.java
*
*/
public class TestFakeSegmentForJni {
public static void main(String[] args) throws Exception {
System.out.println ("In this project, we test jni!\n");
String s = FakeSegmentForJni.SegmentALine("now we test FakeSegmentForJni");
System.out.print(s);
}
}
回到jni1目录下,执行[hadoop@Master jni1]$ javac -d ./bin ./src/*.java,jni1/bin/FakeSegmentForJni目录下会多出一个TestFakeSegmentForJni.class文件。
7.打成jar包。
回到bin目录下,执行[hadoop@Master bin]$ jar -cvf TestFakeSegmentForJni.jar FakeSegmentForJni,jar命令把当前FakeSegmentForJni文件夹下的所有class文件都打成了一个jar包。所以,bin目录下会多出个.jar文件。
8.运行。 [hadoop@Master bin]$ java -Djava.library.path='.' -cp TestFakeSegmentForJni.jar FakeSegmentForJni.TestFakeSegmentForJni
这里不知道为什么,用-jar来运行的话,会出现“no main manifest attribute, in TestFakeSegmentForJni.jar”的错误,改成-cp就好了。
运行结果如下: [hadoop@Master bin]$ java -Djava.library.path='.' -cp TestFakeSegmentForJni.jar FakeSegmentForJni.TestFakeSegmentForJni
In this project, we test jni!
now we test FakeSegmentForJni--copy that
public static class ReduceTestJni extends Reducer {
protected void reduce(Text key, Iterable values, Context context)
throws IOException, InterruptedException {
String outString = "";
for (Text value: values)
{
outString = value.toString();
context.write(key, new Text(outString));
}
}
}
public void runTestJni (String[] args) throws Exception {
// the configuration
Configuration conf = new Configuration();
// conf.set("mapred.job.tracker", "192.168.178.92:9001");
// conf.addResource(new Path("E:\\HadoopWorkPlat\\hadoop-1.1.2\\conf\\hdfs-site.xml"));
// String[] ars = { "/user/hadoop/0", "/user/hadoop/colorToGrayOutput2" };
GenericOptionsParser goparser = new GenericOptionsParser(conf, args);
String otherargs [] = goparser.getRemainingArgs();
removeDir(new Path(args[2]), conf);
// the job
Job job;
job = new Job(conf, "@here-TestFakeSegmentForJni-hadoopJni");
job.setJarByClass(JniTest.class);
// the mapper
job.setMapperClass(MapTestJni.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
// the reducer
job.setReducerClass(ReduceTestJni.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
job.setNumReduceTasks(1);
// the path
FileInputFormat.addInputPath(job, new Path(otherargs[1]));
FileOutputFormat.setOutputPath(job, new Path(otherargs[2]));
job.waitForCompletion(true);
}
public static void main(String[] args) throws Exception {
System.out.println ("In this project, we test jni!\n");
// test jni on linux local
/*String s = FakeSegmentForJni.SegmentALine("now we test FakeSegmentForJni");
System.out.print(s);*/
// test jni on hadoop
new JniTest().runTestJni(args);
} // main
@SuppressWarnings("deprecation")
public static void removeDir(Path outputPath, Configuration conf)
throws IOException {
FileSystem fs = FileSystem.get(conf);
if (fs.exists(outputPath)) {
fs.delete(outputPath);
}
}
}
其中runTestJni(),GenericOptionsParser那两行是关键,用来分析和执行hadoop命令中传进来的特殊参数。配合命令行中的命令(下文写),他把动态库分发到tasknode上,路径与jar的执行路径相同。
通常用jar包的形式运行hadoop程序,所需的参数,如:输入路径、输出路径、mapper、combiner、reducer等都可以用Job来设置,不需要额外的参数。命令行中少了这些参数,会显得短很多。尤其hadoop命令行一般都挺长,就很方便。相反地,采用c++ streaming的方式来运行程序的时候,就需要用-input、-output等参数来指定相关参数。不过,在jar包中,除了用Job外,也可以用GenericOptionsParser来解析上述命令行中的参数,只要命令行配合有相应的输入,GenericOptionsParser就可以解析。对于-input、-output等来讲,没有必要这样做。还有一个参数是-files,就是把-files后面的文件(多个文件用逗号间隔)同jar包一起分发到tasknode中,这个参数,刚好可以将我们的动态库分发下去。
“goparser.getRemainingArgs();”这条语句,是在GenericOptionsParser解析完特殊参数之后,获得剩下的参数列表,对于我们来讲,剩下的参数就是main函数所在的类名、输入路径和输出路径,参见下面的命令行。
2.把工程打包成jar包。Export->Runnable JAR file->launch configuration(选择相应的工程)->Export destination->Extract required libraries into generated JAR.
3.把生成的jar包传到Master上。这里的jar是JniTest3.jar。 [hadoop@Master ~]$ ll -a | grep JniTest3.jar
-rw-r--r-- 1 hadoop hadoop 27495223 Nov 28 13:14 JniTest3.jar
4.执行。 [hadoop@Master ~]$ hadoop jar JniTest3.jar -files /home/hadoop/Documents/java/jni1/bin/libFakeSegmentForJni.so FakeSegmentForJni.TestFakeSegmentForJni input output
hadoop jar 命令后面跟随的第一个参数一定是打好的jar包,在本例中是TestFakeSegmentForJniHadoop.jar文件及其路径
由于在控制函数中用了GenericOptionsParser,jar包后面就必须紧跟需要设定的参数,这里,我们的参数是“-files /xxx/TestJni/libFakeSegmentForJni.so”,表示把本地路径“/xxx/TestJni/”中的libFakeSegmentForJni.so文件随jar包分发下去。
剩下的就比较容易了,分别是main函数所在的类名、输入路径、输出路径
5.结果。
output中的part-r-00000中的内容是key jni-value--copy that
至此,调用成功。
FAQ:
1.返回int[]型数据。主要代码如下:
JNIEXPORT jintArray JNICALL Java_ArrayTest_initIntArray(JNIEnv *env, jclass cls, int size)
{
jintArray result;
result = (*env)->NewIntArray(env, size);
if (result == NULL) {
return NULL; /* out of memory error thrown */
}
int i;
// fill a temp structure to use to populate the java int array
jint fill[256];
for (i = 0; i < size; i++) {
fill = 0; // put whatever logic you want to populate the values here.
}
// move from the temp structure to the java structure
(*env)->SetIntArrayRegion(env, result, 0, size, fill);
return result;
}