JNI 中的數組分爲基本類型數組和對象數組,它們的處理方式是不同的,基本類型數組中的全部元素都是 JNI 的基本數據類型,能夠直接訪問。而對象數組中的全部元素是一個類的實例或其它數組的引用,和字符串操做同樣,不能直接訪問 Java 傳遞給 JNI 層的數組,必須選擇合適的 JNI 函數來訪問和設置 Java 層的數組對象。java
// // Created by Peng Cai on 2018/10/10. // #include <jni.h> #include <stdlib.h> #include <string.h> JNIEXPORT jint JNICALL Java_org_professor_jni_bean_Student_sum(JNIEnv *env, jobject instance, jintArray stuScore_) { //GetIntArrayElements 第三個參數表示返回的數組指針是原始數組, // 仍是拷貝原始數據到臨時緩衝區的指針,若是是 JNI_TRUE:表示臨時緩衝區數組指針, // JNI_FALSE:表示臨時原始數組指針。開發當中,咱們並不關心它從哪裏返回的數組指針, // 這個參數填 NULL 便可,但在獲取到的指針必須作校驗,由於當原始數據在內存當中不是連續存放的狀況下, // JVM 會複製全部原始數據到一個臨時緩衝區,並返回這個臨時緩衝區的指針。 // 有可能在申請開闢臨時緩衝區內存空間時,會內存不足致使申請失敗,這時會返回 NULL。 //NULL 至關於 JNI_FALSE 表明不拷貝數組中的內容到緩衝區 //JNI_TRUE 拷貝數組中的內容到緩衝區 //*stuScore 指針指向一個int 類型數組 //C語言的內存有程序員來管理也就是說 手動手動分配與釋放 //調用該函數式最安全的 當GC掃描到stuScore_該對象,會給該對象加鎖,本地方法會處於阻塞狀態(block) //可能數組中的元素在內存中是不連續的,JVM可能會複製全部原始數據到緩衝區,而後返回這個緩衝區的指針 jint *stuScore = (*env)->GetIntArrayElements(env, stuScore_, NULL); if(stuScore == NULL){ return 0; //JVM複製原始數據到緩衝區失敗 } int sum = 0; int length = (*env)->GetArrayLength(env, stuScore); for (int i = 0; i < length; ++i) { sum += stuScore[i]; } //釋放stuScore 指向的緩衝區 (*env)->ReleaseIntArrayElements(env, stuScore_, stuScore, 0); return sum; } //這種寫法傳遞數組元素很是少時候效率高 JNIEXPORT jfloat JNICALL Java_org_professor_jni_bean_Student_average(JNIEnv *env, jobject instance, jfloatArray stuScore_) { // jfloat *stuScore = (*env)->GetFloatArrayElements(env, stuScore_, NULL); jsize length = (*env)->GetArrayLength(env, stuScore_); //建立數組 float *stuScoreTmp = (float *) malloc(sizeof(jfloat) * length); //申請緩衝區 memset(stuScoreTmp,0, sizeof(jfloat)*length);//初始化緩衝區 (*env)->GetFloatArrayRegion(env, stuScore_, 0, length, stuScoreTmp); //拷貝Java數組中的全部元素到緩衝區中 int sum = 0; for (int i = 0; i < length; i++) { sum += *(stuScoreTmp + i); } jfloat ave = sum / length; free(stuScoreTmp);// 釋放存儲數組元素的緩衝區 // (*env)->ReleaseFloatArrayElements(env, stuScore_, stuScore, 0); return ave; } JNIEXPORT jfloat JNICALL Java_org_professor_jni_bean_Student_ave(JNIEnv *env, jobject instance, jfloatArray stuScore_) { //(*GetPrimitiveArrayCritical)(JNIEnv*, jarray, jboolean*); //直接獲取一個指針獲取原始數組 調用該函數會暫停GC線程,不能調用其餘線程的阻塞或者等待式函數(wait notify) jfloat *stuScore = (*env)->GetPrimitiveArrayCritical(env, stuScore_, NULL); jsize length = (*env)->GetArrayLength(env, stuScore_); float sum = 0; for (int i = 0; i < length; i++) { sum += *(stuScore + i); } float ave = sum / length; (*env)->ReleasePrimitiveArrayCritical(env, stuScore_, stuScore, 0); //釋放須要與上面對應 return ave; }
在 Java 中建立的對象全都由 GC(垃圾回收器)自動回收,不須要像 C/C++ 同樣須要程序員本身管理內存。GC 會實時掃描全部建立的對象是否還有引用,若是沒有引用則會當即清理掉。當咱們建立一個像 int 數組對象的時候,當咱們在本地代碼想去訪問時,發現這個對象正被 GC 線程佔用了,這時本地代碼會一直處於阻塞狀態,直到等待 GC 釋放這個對象的鎖以後才能繼續訪問。爲了不這種現象的發生,JNI 提供了 Get/ReleasePrimitiveArrayCritical這對函數,本地代碼在訪問數組對象時會暫停 GC 線程。不過使用這對函數也有個限制,在 Get/ReleasePrimitiveArrayCritical 這兩個函數期間不能調用任何會讓線程阻塞或等待 JVM 中其它線程的本地函數或JNI函數,和處理字符串的 Get/ReleaseStringCritical 函數限制同樣。這對函數和 GetIntArrayElements 函數同樣,返回的是數組元素的指針。程序員
JNI 提供了兩個函數來訪問對象數組,GetObjectArrayElement
返回數組中指定位置的元素,SetObjectArrayElement
修改數組中指定位置的元素。與基本類型不一樣的是,咱們不能一次獲得數據中的全部對象元素或者一次複製多個對象元素到緩衝區。由於字符串和數組都是引用類型,只能經過 Get/SetObjectArrayElement 這樣的 JNI 函數來訪問字符串數組或者數組中的數組元素。數組
JNIEXPORT jobjectArray JNICALL Java_org_professor_jni_bean_Student_initInt2DArray(JNIEnv *env, jobject instance, jint size) { jobjectArray result; jclass clsIntArray; jint i, j; // 1.得到一個int型二維數組類的引用 clsIntArray = (*env)->FindClass(env, "[I"); if (clsIntArray == NULL) { return NULL; } // 2.建立一個數組對象(裏面每一個元素用clsIntArray表示) result = (*env)->NewObjectArray(env, size, clsIntArray, NULL); if (result == NULL) { return NULL; } // 3.爲數組元素賦值 for (i = 0; i < size; ++i) { jint buff[256]; jintArray intArr = (*env)->NewIntArray(env, size); if (intArr == NULL) { return NULL; } for (j = 0; j < size; j++) { buff[j] = i + j; } (*env)->SetIntArrayRegion(env, intArr, 0, size, buff); (*env)->SetObjectArrayElement(env, result, i, intArr); (*env)->DeleteLocalRef(env, intArr); } return result; }
本地函數 initInt2DArray
首先調用 JNI 函數 FindClass
得到一個 int 型的二維數組類的引用,傳遞給FindClass 的參數"[I"是 JNI class descript(JNI 類型描述符,後面爲詳細介紹),它對應着 JVM 中的int[]類型。若是 int[]類加載失敗的話,FindClass
會返回 NULL,而後拋出一個java.lang.NoClassDefFoundError: [I異常
。安全
接下來,NewObjectArray
建立一個新的數組,這個數組裏面的元素類型用 intArrCls(int[])
類型來表示。函數NewObjectArray
只能分配第一維,JVM 沒有與多維數組相對應的數據結構,JNI 也沒有提供相似的函數來建立二維數組。因爲 JNI 中的二維數組直接操做的是 JVM 中的數據結構,相比 JAVA 和 C/C++建立二維數組要複雜不少。給二維數組設置數據的方式也很是直接,首先用 NewIntArray
建立一個 JNI 的 int 數組,併爲每一個數組元素分配空間,而後用 SetIntArrayRegion
把 buff[]
緩衝中的內容複製到新分配的一維數組中去,最後在外層循環中依次將 int[]數組賦值到 jobjectArray
數組中,一維數組中套一維數組,就造成了一個所謂的二維數組。數據結構
另外,爲了不在循環內建立大量的 JNI 局部引用,形成 JNI 引用表溢出,因此在外層循環中每次都要調用DeleteLocalRef
將新建立的 jintArray 引用從引用表中移除。在 JNI 中,只有 jobject
以及子類屬於引用變量,會佔用引用表的空間,jint,jfloat,jboolean 等都是基本類型變量,不會佔用引用表空間,即不須要釋放。引用表最大空間爲 512 個,若是超出這個範圍,JVM 就會掛掉。函數