androidobject数组赋值_AndroidJNI开发系列(七)访问数组JNI访问数组
JNI 中的数组分为基本类型数组和对象数组,它们的处理⽅式是不⼀样的,基本类型数组中的所有元素都是 JNI 的基本数据类型,可以直接访问。⽽对象数组中的所有元素是⼀个类的实例或其它数组的引⽤,和字符串操作⼀样,不能直接访问 Java 传递给 JNI 层的数组,必须选择合适的 JNI 函数来访问和设置 Java 层的数组对象。
访问基本数据类型数组
//
// Created by Peng Cai on 2018/10/10.
//
#include
#include
#include
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 函数⼀样,返回的是数组元素的指针。
⼩结
对于⼩量的、固定⼤⼩的数组,应该选择 Get/SetArrayRegion 函数来操作数组元素是效率最⾼的。因
为这对函数要求提前分配⼀个 C 临时缓冲区来存储数组元素,你可以直接在 Stack(栈)上或⽤ malloc 在堆上来动态申请,当然在栈上申请是最快的。有童鞋可能会认为,访问数组元素还需要将原始数据全部拷贝⼀份到临时缓冲区才能访问⽽觉得效率低?我想告诉你的是,像这种复制少量数组元素的代价是很⼩的,⼏乎可以忽略。这对函数的另外⼀个优点就是,允许你传⼊⼀个开始索引和长度来实现对⼦数组元素的访问和操作(SetArrayRegion函数可以修改数组),不过传⼊的索引和长度不要越界,函数会进⾏检查,如果越界了会抛出 ArrayIndexOutOfBoundsException 异常。
如果不想预先分配 C 缓冲区,并且原始数组长度也不确定,⽽本地代码⼜不想在获取数组元素指针时被阻塞的话,使⽤
Get/ReleasePrimitiveArrayCritical 函数对,就像 Get/ReleaseStringCritical 函数对⼀样,使⽤这对函数要⾮常⼩⼼,以免死锁。
Get/ReleaseArrayElements 系列函数永远是安全的,JVM 会选择性的返回⼀个指针,这个指针可能指向原始数据,也可能指向原始数据的复制。
访问对象数组
JNI 提供了两个函数来访问对象数组,GetObjectArrayElement 返回数组中指定位置的元素,SetObje
ctArrayElement 修改数组中指定位置的元素。与基本类型不同的是,我们不能⼀次得到数据中的所有对象元素或者⼀次复制多个对象元素到缓冲区。因为字符串和数组都是引⽤类型,只能通过 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;java定义一维数组并赋值
}
本地函数 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 就会挂掉。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论