java codebase的Hadoop应用中使用Darts

18 篇文章 0 订阅
15 篇文章 0 订阅

背景

Darts是双数组的c++ (template)实现,用来存储、查询大规模词表非常方便。缺点是只能够使用“字符串完全匹配”,而不能查询“某个字符串是否是另外一个字符串的字串”这样的需求。

如下原因结合起来,促使要在java codebase中使用Darts:

1.        词表规模巨大,存储、查询效率很低

2.        Darts是c++实现,现有Hadoop codebase是java的,无法直接使用

技术思路:

1.        用c++进一步包装Darts接口(这一步必须要做,因为darts实现在.h中——template+inline牛B,默认使用方式是在c++project中直接包含头文件就能调用相关接口),并生成动态链接库

2.        用JNI(java native interface)进一步包装动态库,形成java接口

3.        在现有codebase中调用JNI

使用方法

这部分讲如何使用已经包装好的Darts。具体Darts是如何包装的,后面再详细说。

Darts JNI接口

接口定义在Darts.java文件中:

public class Darts {
	public static native boolean BuildDict (String fileInput, String fileDictOutput);
	public static native boolean LoadDict (String fileDict);
	public static native boolean IsInDict (String term);
	
	static
	{
		System.loadLibrary("Darts");
	}
	
	
	public static void main(String[] args) throws Exception {

		System.out.println ("In this project, we test jni!\n");
		
		…… 
		
	} // main
}

三个接口,分别创建词典、加载词典和判断某个字符串是否在词典中。

创建Darts词典

1.        将词典放到文本文件中,以UTF-8保存,在windows平台,UE中可以选择“UTF-8-无BOM”

2.        在java中调用BuildDict函数,输入参数为步骤1中的文本文件,输出为Darts格式的词典文件,例如:在Darts.java的main函数中作如下调用:

Darts.BuildDict("/xxx/xxx_utf8.txt", "/xxx/xxx_utf8.dict");

3.        运行上述调用程序,生成词典文件

如果在windows + eclipse上直接运行,会提示找不到动态库Darts,是在这里调用的:

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

实际上是找不到libDarts.so文件。而且在windows环境下,.so文件也无法正常加载。解决方法是打成jar包,放到linux环境下,用‘-Djava.library.path’选项来指明.so文件所在的路径,例如:


java -Djava.library.path='/xxx/MyDarts'-jar /xxx/tmp/DartsForJni.jar myDarts

我编译的libDarts.so文件在'/xxx/MyDarts'路径下。

Hadoop上使用Darts词典

1.        在map class的setup函数中load词典,调用Darts.LoadDict函数

protected void setup(Context context) throws IOException, InterruptedException
		{
			if (!Darts.LoadDict("xxx_utf8.dict"))
				return;
		}

2.        在map或者reduce class函数中使用词典,调用Darts.IsInDict函数

protected void map(Writable key, Text value, Context context)
		throws IOException, InterruptedException {
			
			String line = value.toString().trim();
			if (Darts.IsInDict(line.toString()))
			{
				context.write(new Text(line.toString()), new LongWritable(1));
			}
		}

注意!一定要确定line中用的是utf8编码格式的字符。而且,我看JNI(jni.h)中的接口函数,似乎字符串值能够用utf8的;或者即便是用它的jchar转换编码,也比较麻烦。这个是jni.h中的接口片段:

jstring (JNICALL *NewStringUTF)
      (JNIEnv *env, const char *utf);
jsize (JNICALL *GetStringUTFLength)
      (JNIEnv *env, jstring str);
const char* (JNICALL *GetStringUTFChars)
      (JNIEnv *env, jstring str, jboolean *isCopy);
void (JNICALL *ReleaseStringUTFChars)
      (JNIEnv *env, jstring str, const char* chars);

在实际实现libDarts.so的时候,调用的是GetStringUTFChars。为避免麻烦,也就统一用utf8好了。如果不确定读取数据的编码,可以用以下方式转换编码:

String line = value.toString().trim();
String line_utf8 = new String (line.getBytes("UTF-8"), "UTF-8");
if (Darts.IsInDict(line_utf8.toString()))
{
context.write(new Text(line_utf8.toString()), new LongWritable(1));
}

3.        提交hadoop任务

提交的时候,需要将libDarts.so和词典文件(我的是xxx_utf8.dict)同jar包一起上传,并且在运行时能够正确找到libDarts.so所在的路径。需要两方面工作:

a)        在hadoop控制函数中加入如下代码

Configuration conf = new Configuration();
GenericOptionsParser goparser = new GenericOptionsParser(conf, args);
String otherargs [] = goparser.getRemainingArgs();

b)        命令行用‘-files’指定要同jar包一起分发到tasknode上的文件,如:

hadoop jar /xxx/tmp/DartsForJniHadoop.jar -files /xxx/MyDarts/libDarts.so,/xxx/xxx_utf8.dict MyDarts.Darts /xxx/tmp1/aaa.txt /xxx/tmp2

重点是‘-files’后面的两个文件,都是本地路径。再往后的参数分别是:main函数所在的包、输入路径、输出路径。

高级知识——JNI

这一部分讲一下怎么用JNI来调用c++的东西。结合上面的内容,就是libDarts.so是怎么生成的。讲完这个,就完整了。这两天尝试的就是这些内容。

JNI是javanative interface的缩写。所谓的’native’就是指本地的环境,与Java虚拟机环境相对。

接下来以libDarts.so为例,讲讲这东西怎么生成的。

1.        首先定义java接口,对应上文的内容,就是写com.sohu.bright.tool.Darts.java文件中的内容。注意文件中的函数接口都有‘native’关键字,表明这是一个native函数,不在javacodebase中实现,而由外部来实现。

2.        编译com.sohu.bright.tool.Darts.java文件,生成Darts.class文件。我用的eclipse,自动编译生成。在linux下用’java’命令即可。

3.        用javah命令,从Darts.class文件中生成c/c++的头文件,如:

javah -jni -classpath . MyDarts.Darts

注意!需要注意路径。Java中相对Darts.class的当前路径(-classpath后面那个“.”)指的是“MyDarts\Darts.class”,是与package相关的。

生成的头文件MyDarts_Darts.h,内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class MyDarts_Darts */

#ifndef _Included_MyDarts_Darts
#define _Included_MyDarts_tool_Darts
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     MyDarts_Darts
 * Method:    BuildDict
 * Signature: (Ljava/lang/String;Ljava/lang/String;)Z
 */
JNIEXPORT jboolean JNICALL Java_MyDarts_Darts_BuildDict
  (JNIEnv *, jclass, jstring, jstring);

/*
 * Class:     MyDarts_Darts
 * Method:    LoadDict
 * Signature: (Ljava/lang/String;)Z
 */
JNIEXPORT jboolean JNICALL Java_MyDarts_Darts_LoadDict
  (JNIEnv *, jclass, jstring);

/*
 * Class:     MyDarts_Darts
 * Method:    IsInDict
 * Signature: (Ljava/lang/String;)Z
 */
JNIEXPORT jboolean JNICALL Java_MyDarts_Darts_IsInDict
  (JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

其实这东西也能人来写,不必从.class文件中生成;不过自动生成更方便一些、也更标准。

4.        写MyDarts_Darts.cpp文件,实现MyDarts_Darts.h中定义的接口,内容如下:

#include <jni.h>

#include "darts.h"
#include "MyDarts_Darts.h"

#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <algorithm>

using namespace std;

// the global variable for dictionary
Darts::DoubleArray thedict;

/*
 * Class:     MyDarts_Darts
 * Method:    BuildDict
 * Signature: (Ljava/lang/String;Ljava/lang/String;)Z
 */
JNIEXPORT jboolean JNICALL Java_MyDarts_Darts_BuildDict
  (JNIEnv *env, jclass obj, jstring fileInput, jstring fileDictOutput)
{
	// get the file name strings
	const char *pFileInput = NULL;
	const char *pFileDictOutput = NULL;
	pFileInput = env->GetStringUTFChars (fileInput, false);
	pFileDictOutput = env->GetStringUTFChars (fileDictOutput, false);
	if (pFileInput == NULL || pFileDictOutput == NULL)
		return false;
	
	// load the terms and sort
	ifstream in (pFileInput);
	if (!in)
	{
		cerr << "Can not open the file of " << pFileInput << endl;
		return false;
	}
	
	string sTerm;
	vector<string> TermVec;
	while (getline (in, sTerm))
	{
		// trim it
		if ('\n' == sTerm.at (sTerm.length()-1) 
			|| '\t' == sTerm.at (sTerm.length()-1))
		{
			if ('\n' == sTerm.at (sTerm.length()-2) 
				|| '\t' == sTerm.at (sTerm.length()-2))
				sTerm.erase (sTerm.length()-2);
			else
				sTerm.erase (sTerm.length()-1);
		}
		TermVec.push_back(sTerm);
	}
	sort (TermVec.begin(), TermVec.end());	// it's necessary to build dictionary for darts
	
	// make TermVec as a c-style string array, and make the value array
	char ** pTermAry = (char **)malloc (TermVec.size() * sizeof(char *));
	vector<string>::iterator pIter = TermVec.begin();
	int i = 0;
	while (pIter != TermVec.end())
	{
		pTermAry[i] = (char*)pIter->c_str();
		pIter++;
		i++;
	}
	
	// build the dictionary for darts and output it
	thedict.build (TermVec.size(), pTermAry, 0, 0);
	thedict.save (pFileDictOutput);
	
	// clean up
	free (pTermAry);
	env->ReleaseStringUTFChars(fileInput, pFileInput);
	env->ReleaseStringUTFChars(fileDictOutput, pFileDictOutput);
	
	return true;
}


/*
 * Class:     MyDarts_Darts
 * Method:    LoadDict
 * Signature: (Ljava/lang/String;)Z
 */
JNIEXPORT jboolean JNICALL Java_MyDarts_Darts_LoadDict
  (JNIEnv *env, jclass obj, jstring fileDictInput)
{
	// get the file name strings
	const char *pFileDictInput = NULL;
	pFileDictInput = env->GetStringUTFChars (fileDictInput, false);
	if (pFileDictInput == NULL)
		return false;
	
	// load the dictionary
	if (-1 == thedict.open (pFileDictInput))
	{
		cerr << "Can not open the file of " << pFileDictInput << endl;
		return false;
	}
	env->ReleaseStringUTFChars(fileDictInput, pFileDictInput);
	return true;
}

/*
 * Class:     MyDarts_Darts
 * Method:    IsInDict
 * Signature: (Ljava/lang/String;)Z
 */
JNIEXPORT jboolean JNICALL Java_MyDarts_Darts_IsInDict
  (JNIEnv *env, jclass obj, jstring term)
{
	// get the file name strings
	const char *pTerm = NULL;
	pTerm = env->GetStringUTFChars (term, false);
	if (pTerm == NULL)
		return false;
		
	int iVal = 0;
	thedict.exactMatchSearch (pTerm, iVal);
	env->ReleaseStringUTFChars(term, pTerm);
	if (-1 == iVal)
		return false;
	else
		return true;
}
仔细看一下,接口中的数据类型都是JNI中的类型,如:jboolean、jstring等等,作为java数据类型和c++数据类型的桥梁,两者能够通过JNI函数相互转换。JNI编程可以查看具体资料,这里不多说。需要提醒一点的是,JNI中获取字符串的函数是 GetStringUTFChars,将java String类型的字符串转成c字符串。从函数名字可以看到,它 获取\返回的是UTF8编码的字符串,这也是上文强调 无论是在构建字典的时候、还是在查询的时候,都要使用UTF8编码字符串的根本原因——不是Darts工具限制,而是JNI有这样的限制

5.        g++生成动态库

命令如下:

g++ -I/usr/lib/jvm/java/include-I/usr/lib/jvm/java/include/linuxMyDarts_Darts.cpp -fPIC-shared -o libDarts.so

需要注意两个地方:

a)        需要注意的是一定要用‘-I’参数将包含jni.h的路径指明,否则找不到头文件,编译出错。

b)        文件名一定要用‘libxxx.so’,其中‘xxx’是动态库的名字,也是代码(无论java还是c++)中load动态库的名字。其余的,‘lib’小写。最常犯的错误是将文件名指定为‘xxx.so’。

至此,libDarts.so文件生成,按照上文的方法就可以正常使用。


参考文献:

【基础知识】使用Darts


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值