文档章节

Android JNI开发系列(七)访问数组

蔡小鹏
 蔡小鹏
发布于 10/14 11:46
字数 2281
阅读 205
收藏 2

JNI访问数组

JNI 中的数组分为基本类型数组和对象数组,它们的处理方式是不一样的,基本类型数组中的所有元素都是 JNI 的基本数据类型,可以直接访问。而对象数组中的所有元素是一个类的实例或其它数组的引用,和字符串操作一样,不能直接访问 Java 传递给 JNI 层的数组,必须选择合适的 JNI 函数来访问和设置 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 函数一样,返回的是数组元素的指针。

小结

  • 对于小量的、固定大小的数组,应该选择 Get/SetArrayRegion 函数来操作数组元素是效率最高的。因为这对函数要求提前分配一个 C 临时缓冲区来存储数组元素,你可以直接在 Stack(栈)上或用 malloc 在堆上来动态申请,当然在栈上申请是最快的。有童鞋可能会认为,访问数组元素还需要将原始数据全部拷贝一份到临时缓冲区才能访问而觉得效率低?我想告诉你的是,像这种复制少量数组元素的代价是很小的,几乎可以忽略。这对函数的另外一个优点就是,允许你传入一个开始索引和长度来实现对子数组元素的访问和操作(SetArrayRegion函数可以修改数组),不过传入的索引和长度不要越界,函数会进行检查,如果越界了会抛出 ArrayIndexOutOfBoundsException 异常。
  • 如果不想预先分配 C 缓冲区,并且原始数组长度也不确定,而本地代码又不想在获取数组元素指针时被阻塞的话,使用 Get/ReleasePrimitiveArrayCritical 函数对,就像 Get/ReleaseStringCritical 函数对一样,使用这对函数要非常小心,以免死锁。
  • Get/ReleaseArrayElements 系列函数永远是安全的,JVM 会选择性的返回一个指针,这个指针可能指向原始数据,也可能指向原始数据的复制。

访问对象数组

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 数组,并为每个数组元素分配空间,然后用 SetIntArrayRegionbuff[]缓冲中的内容复制到新分配的一维数组中去,最后在外层循环中依次将 int[]数组赋值到 jobjectArray 数组中,一维数组中套一维数组,就形成了一个所谓的二维数组。

另外,为了避免在循环内创建大量的 JNI 局部引用,造成 JNI 引用表溢出,所以在外层循环中每次都要调用DeleteLocalRef 将新创建的 jintArray 引用从引用表中移除。在 JNI 中,只有 jobject 以及子类属于引用变量,会占用引用表的空间,jint,jfloat,jboolean 等都是基本类型变量,不会占用引用表空间,即不需要释放。引用表最大空间为 512 个,如果超出这个范围,JVM 就会挂掉。

© 著作权归作者所有

共有 人打赏支持
蔡小鹏
粉丝 30
博文 42
码字总数 72954
作品 0
海淀
Android工程师
私信 提问
Android JNI(一)——NDK与JNI基础

本系列文章如下: Android JNI(一)——NDK与JNI基础 Android JNI学习(二)——实战JNI之“hello world” Android JNI学习(三)——Java与Native相互调用 Android JNI学习(四)——JNI的常用方法...

隔壁老李头
05/09
0
0
Android平台上的JNI技术介绍

NDK简介 Android是由Google领导开发的操作系统,Android依靠其开放性,迅速普及,成为目前最流行的智能手机操作系统。 图0-1 Android系统架构图 图0-1是Android系统架构图。大多数程序位于最...

ChowJames
2012/10/01
0
0
Android JNI开发系列(十)JNI访问 Java 实例变量和静态变量

JNI访问 Java 实例变量和静态变量 Java 中的实例变量和静态变量,在本地代码中如何来访问和修改。静态变量也称为类变量(属性),在所有实例对象中共享同一份数据,可以直接通过类名.变量名来...

蔡小鹏
10/16
0
0
Android 网络编程 目录

Android 网络编程 目录 Android 网络编程1 Http协议 Android 网络编程2 Okhttp缓存机制 Android 网络编程3 Java NIO to be continued... Android 架构师之路 目录 Android 架构师之路1 UML图...

香沙小熊
06/21
0
0
C语言自学完备手册(29)——指针(3)

版权声明: https://blog.csdn.net/lfdfhl/article/details/83274203 自定义View系列教程00–推翻自己和过往,重学自定义View 自定义View系列教程01–常用工具介绍 自定义View系列教程02–o...

谷哥的小弟
10/22
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Win10:默认的图片打开应用,打开图片时速度明显很慢的解决办法

首先,我们随便地打开一张图片。然后,点击右上角的三个小点,最后点击弹出菜单最下面的“设置”。如下图: 在“设置”中找到下面的“人物”,把它关掉就好了。 原来,默认情况下,Win 10的图...

LivingInFHL
38分钟前
2
0
js代码激发onchange事件,兼容谷歌火狐IE

var el = document.getElementsByName('role')[0]; el.value = '3'; var evt = document.createEvent("HTMLEvents"); evt.initEvent("change", false, true); el.dispatchEvent(evt);......

我退而结网
52分钟前
3
0
mysql客户端报错:libmysqlclient_16 not defined in file libmysqlclient.so.16

报错情况: 安装完mydumper之后(上一篇文章),登陆Mysql客户端报错:version libmysqlclient_16 not defined in file libmysqlclient.so.16 with link time reference 同样:mysql的其他客...

machogyb
59分钟前
1
0
MySQL 数据库中间件 安装部署测试全过程

1、环境准备 1.1、操作系统环境 [root@MyCat conf]# uname -aLinux MyCat 2.6.32-431.el6.x86_64 #1 SMP Sun Nov 10 22:19:54 EST 2013 x86_64 x86_64 x86_64 GNU/Linux 1.2、关闭SELIN......

PeakFang-BOK
今天
6
0
Linux Mysql 安装

https://www.cnblogs.com/xinjing-jingxin/p/8025805.html

流氓兔-
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部