Android JNI開發系列(七)訪問數組

JNI訪問數組

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 函數同樣,返回的是數組元素的指針。程序員

小結

  • 對於小量的、固定大小的數組,應該選擇 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 就會掛掉。函數

相關文章
相關標籤/搜索