前提
Java HashMap 是基於哈希表的 Map 接口的實現。此實現提供所有可選的映射操作,並允許使用null
值和null
鍵。HashMap是存放引用類型數據的容器,只能存放引用數據類型,不能存放如int、long等基礎類型的數據。
這裏用實際的例子來演示如何解析HashMap,在這個Sample中,HashMap作爲參數從Java傳遞到Native(C/C++)層,然後在C代碼中解析HashMap中的數據。Java中使用HashMap<String, Integer>
存放學生成績 “課程 - 分數”鍵值對,然後把這個學生成績HashMap數據傳遞到Native中,用C/C++解析其中的數據,然後以字符串的形式把成績信息返回到Java。
準備工作:Java中構造和解析HashMap
首先,在Java中構造一個課程分數HashMap。
public HashMap<String, Integer> constructHashMap() {
HashMap<String, Integer> courseScore = new HashMap<>();
courseScore.put("Chinese", 90);
courseScore.put("Math", 95);
courseScore.put("Physics", 100);
courseScore.put("Biology", 85);
return courseScore;
}
在Java中實現返回字符串需求的話,可以使用下面的代碼來解析HashMap中的數據。
public String parseHashMap(HashMap<String, Integer> courseScore){
StringBuilder info = new StringBuilder();
Iterator iter = courseScore.entrySet().iterator();
while (iter.hasNext()){
Map.Entry entry = (Map.Entry) iter.next();
String course = (String) entry.getKey();
Integer score = (Integer) entry.getValue();
info.append(course+": ");
info.append(score+". ");
}
return String.valueOf(info);
}
在主函數中依次調用constructHashMap()
和parseHashMap()
函數,會返回如下所示的字符串:
"Physics: 100. Math: 95. Chinese: 90. Biology: 85. "
Native層雖然使用的是C/C++編程語言,但是使用的確實Java的處理邏輯。在Native中解析HashMap數據結構就要完全按照parseHashMap()
函數的邏輯進行數據處理。說白了,就是使用C/C++來模仿Java完成相應操作。
在Native中模仿Java操作其實不難,只是比較 繁瑣 !本來Java一句代碼可能就需要四五句甚至更多語句來實現相同的功能。爲了在Native中能更容易的模仿Java實現,現在我們來把上面的parseHashMap()
函數中的語句儘量使用簡單語句來實現。
public String parseHashMap2(HashMap<String, Integer> courseScore){
String info = "";
Set set = courseScore.entrySet();
Iterator iter = set.iterator();
while (iter.hasNext()){
Map.Entry entry = (Map.Entry) iter.next();
String course = (String) entry.getKey();
info = info + course + ": ";
Integer score = (Integer) entry.getValue();
info = info + score.intValue() + ". ";
}
return info;
}
進入正題:用C/C++解析Java HashMap
首先Java中聲明Native函數。
public class JniManager {
...
public native String nGetHashMapInfo(HashMap<String, Integer> info);
}
在JNI_OnLoad
函數中,函數名映射關係結構體JNINativeMethod
中有如下描述。
static const JNINativeMethod gMethods[] = {
{"nGetHashMapInfo", "(Ljava/util/HashMap;)Ljava/lang/String;", (void *) jniGetHashMapInfo}
};
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
...
jint count = sizeof(gMethods) / sizeof(gMethods[0]);
if (env->RegisterNatives(clazz, gMethods, count) != JNI_OK) {
return result;
}
....
}
在上一篇博客【Android JNI】在C/C++中調用Java中講到,調用Java方法的步驟爲:1. 獲取類名jclass,2.獲取方法ID jmethodID,3. 使用實例對象jobject和jmethod調用相應的方法。也就是說每次使用一個方法,都要先通過類獲取到方法ID,並且要得到對象jobject。
我們在jniGetHashMapInfo()
函數中解析Java HashMap<String, Integer>
結構的課程成績數據。
jstring jniGetHashMapInfo(JNIEnv *env, jobject object, jobject hashMapInfo) {
// 用於拼接字符串的數組
char buff[100] = {0};
// 用於拼接字符串的“遊標”指針
char *pos = buff;
if (hashMapInfo == NULL)
return env->NewStringUTF(buff);
// 獲取HashMap類entrySet()方法ID
jclass hashmapClass = env->FindClass("java/util/HashMap");
jmethodID entrySetMID = env->GetMethodID(hashmapClass, "entrySet", "()Ljava/util/Set;");
// 調用entrySet()方法獲取Set對象
jobject setObj = env->CallObjectMethod(hashMapInfo, entrySetMID);
// 調用size()方法獲取HashMap鍵值對數量
// jmethodID sizeMID = env->GetMethodID(hashmapClass, "size", "()I");
// jint size = env->CallIntMethod(hashMapInfo, sizeMID);
// 獲取Set類中iterator()方法ID
jclass setClass = env->FindClass("java/util/Set");
jmethodID iteratorMID = env->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;");
// 調用iterator()方法獲取Iterator對象
jobject iteratorObj = env->CallObjectMethod(setObj, iteratorMID);
// 獲取Iterator類中hasNext()方法ID
// 用於while循環判斷HashMap中是否還有數據
jclass iteratorClass = env->FindClass("java/util/Iterator");
jmethodID hasNextMID = env->GetMethodID(iteratorClass, "hasNext", "()Z");
// 獲取Iterator類中next()方法ID
// 用於讀取HashMap中的每一條數據
jmethodID nextMID = env->GetMethodID(iteratorClass, "next", "()Ljava/lang/Object;");
// 獲取Map.Entry類中getKey()和getValue()的方法ID
// 用於讀取“課程-分數”鍵值對,注意:內部類使用$符號表示
jclass entryClass = env->FindClass("java/util/Map$Entry");
jmethodID getKeyMID = env->GetMethodID(entryClass, "getKey", "()Ljava/lang/Object;");
jmethodID getValueMID = env->GetMethodID(entryClass, "getValue", "()Ljava/lang/Object;");
// HashMap只能存放引用數據類型,不能存放int等基本數據類型
// 使用Integer類的intValue()方法獲取int數據
jclass integerClass = env->FindClass("java/lang/Integer");
jmethodID valueMID = env->GetMethodID(integerClass, "intValue", "()I");
// 循環檢測HashMap中是否還有數據
while (env->CallBooleanMethod(iteratorObj, hasNextMID)) {
// 讀取一條數據
jobject entryObj = env->CallObjectMethod(iteratorObj, nextMID);
// 提取數據中key值:String類型課程名字
jstring courseJS = (jstring) env->CallObjectMethod(entryObj, getKeyMID);
if (courseJS == NULL) // HashMap允許null類型
continue;
// jstring轉C風格字符串
const char *courseStr = env->GetStringUTFChars(courseJS, NULL);
// 提取數據中value值:Integer類型分數,並轉爲int類型
jobject scoreObj = env->CallObjectMethod(entryObj, getValueMID);
if (scoreObj == NULL)
continue;
int score = (int) env->CallIntMethod(scoreObj, valueMID);
// 拼接字符串,sprintf函數返回拼接字符個數
int strLen = sprintf(pos, "%s: ", courseStr);
pos += strLen;
int numLen = sprintf(pos, "%d. ", score);
pos += numLen;
// 釋放UTF字符串資源
env->ReleaseStringUTFChars(courseJS, courseStr);
// 釋放JNI局部引用資源
env->DeleteLocalRef(entryObj);
env->DeleteLocalRef(courseJS);
env->DeleteLocalRef(scoreObj);
}
// 釋放JNI局部引用: jclass jobject
env->DeleteLocalRef(hashmapClass);
env->DeleteLocalRef(setObj);
env->DeleteLocalRef(setClass);
env->DeleteLocalRef(iteratorObj);
env->DeleteLocalRef(iteratorClass);
env->DeleteLocalRef(entryClass);
env->DeleteLocalRef(integerClass);
// 生成jstring字符串並返回
return env->NewStringUTF(buff);
}
調用public native String nGetHashMapInfo(HashMap<String, Integer> info);
函數 和 調用public String parseHashMap2(HashMap<String, Integer> courseScore)
函數效果一樣,都能返回下面這個成績信息字符串。
"Physics: 100. Math: 95. Chinese: 90. Biology: 85. "
如果疑問,歡迎留言。