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 就会挂掉。函数