java調(diào)用本地方法--JNI訪問基本類型數(shù)組

本篇結(jié)構(gòu):

  • 簡(jiǎn)介
  • 實(shí)例

一、簡(jiǎn)介

補(bǔ)充JNI基本類型數(shù)組訪問實(shí)例。

對(duì)于基本數(shù)據(jù)類型數(shù)組,JNI 都有和 Java 相對(duì)應(yīng)的結(jié)構(gòu),在使用起來和基本數(shù)據(jù)類型的使用類似。

JNI 提供了對(duì)應(yīng)的轉(zhuǎn)換函數(shù):GetArrayElements、ReleaseArrayElements。

    intArray = env->GetIntArrayElements(intArray_, NULL);
    env->ReleaseIntArrayElements(intArray_, intArray, 0);

JNI 還提供了如下的函數(shù):

  • GetTypeArrayRegion / SetTypeArrayRegion
    將數(shù)組內(nèi)容復(fù)制到 C 緩沖區(qū)內(nèi),或?qū)⒕彌_區(qū)內(nèi)的內(nèi)容復(fù)制到數(shù)組上。

  • GetArrayLength
    得到數(shù)組中的元素個(gè)數(shù),也就是長(zhǎng)度。

  • NewTypeArray
    返回一個(gè)指定數(shù)據(jù)類型的數(shù)組,并且通過 SetTypeArrayRegion 來給指定類型數(shù)組賦值。

  • GetPrimitiveArrayCritical / ReleasePrimitiveArrayCritical
    如同 String 中的操作一樣,返回一個(gè)指定基礎(chǔ)數(shù)據(jù)類型數(shù)組的直接指針,在這兩個(gè)操作之間不能做任何阻塞的操作。

二、實(shí)例

2.1、編寫Java類

public class IntArray {
    // 在本地代碼中求數(shù)組中所有元素的和
    private native int sumArray(int[] arr);

    public static void main(String[] args) {
        IntArray p = new IntArray();
        int[] arr = new int[10];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
        }
        int sum = p.sumArray(arr);
        System.out.println("sum = " + sum);
    }

    static {
        System.loadLibrary("IntArray");
    }
}

2.2、編譯java類

javac IntArray.java

2.3、生成相關(guān)JNI方法的頭文件

javah -d jnilib -jni IntArray

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class IntArray */

#ifndef _Included_IntArray
#define _Included_IntArray
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     IntArray
 * Method:    sumArray
 * Signature: ([I)I
 */
JNIEXPORT jint JNICALL Java_IntArray_sumArray
  (JNIEnv *, jobject, jintArray);

#ifdef __cplusplus
}
#endif
#endif

2.4、使用C/C++實(shí)現(xiàn)本地方法

// IntArray.c
#include "IntArray.h"
#include <string.h>
#include <stdlib.h>

JNIEXPORT jint JNICALL Java_IntArray_sumArray
(JNIEnv *env, jobject obj, jintArray j_array)
{
    jint i, sum = 0;
    jint *c_array;
    jint arr_len;
    //1. 獲取數(shù)組長(zhǎng)度
    arr_len = (*env)->GetArrayLength(env,j_array);
    //2. 根據(jù)數(shù)組長(zhǎng)度和數(shù)組元素的數(shù)據(jù)類型申請(qǐng)存放java數(shù)組元素的緩沖區(qū)
    c_array = (jint*)malloc(sizeof(jint) * arr_len);
    //3. 初始化緩沖區(qū)
    memset(c_array,0,sizeof(jint)*arr_len);
    printf("arr_len = %d ", arr_len);
    //4. 拷貝Java數(shù)組中的所有元素到緩沖區(qū)中
    (*env)->GetIntArrayRegion(env,j_array,0,arr_len,c_array);
    for (i = 0; i < arr_len; i++) {
        sum += c_array[i];  //5. 累加數(shù)組元素的和
    }
    free(c_array);  //6. 釋放存儲(chǔ)數(shù)組元素的緩沖區(qū)
    return sum;
}

2.5、生成動(dòng)態(tài)鏈接庫(kù)

gcc -D_REENTRANT -fPIC -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -shared -o libIntArray.so IntArray.c

2.6、運(yùn)行java

java -Djava.library.path=jnilib IntArray

2.7、解釋

在Java中定義了一個(gè)sumArray的native方法,參數(shù)類型是int[],對(duì)應(yīng)JNI中jintArray類型。

在本地代碼中,首先通過JNI的GetArrayLength函數(shù)獲取數(shù)組的長(zhǎng)度,已知數(shù)組是jintArray類型,可以得出數(shù)組的元素類型是jint,然后根據(jù)數(shù)組的長(zhǎng)度和數(shù)組元素類型,申請(qǐng)相應(yīng)大小的緩沖區(qū)。如果緩沖區(qū)不大的話,當(dāng)然也可以直接在棧上申請(qǐng)內(nèi)存,那樣效率更高,但是沒那么靈活,因?yàn)镴ava數(shù)組的大小變了,本地代碼也跟著修改。接著調(diào)用GetIntArrayRegion函數(shù)將Java數(shù)組中的所有元素拷貝到C緩沖區(qū)中,并累加數(shù)組中所有元素的和,最后釋放存儲(chǔ)java數(shù)組元素的C緩沖區(qū),并返回計(jì)算結(jié)果。GetIntArrayRegion函數(shù)第1個(gè)參數(shù)是JNIEnv函數(shù)指針,第2個(gè)參數(shù)是Java數(shù)組對(duì)象,第3個(gè)參數(shù)是拷貝數(shù)組的開始索引,第4個(gè)參數(shù)是拷貝數(shù)組的長(zhǎng)度,第5個(gè)參數(shù)是拷貝目的地。

在前面的例子當(dāng)中,通過調(diào)用GetIntArrayRegion函數(shù),將int數(shù)組中的所有元素拷貝到C臨時(shí)緩沖區(qū)中,然后在本地代碼中訪問緩沖區(qū)中的元素來實(shí)現(xiàn)求和的計(jì)算,JNI還提供了一個(gè)和GetIntArrayRegion相對(duì)應(yīng)的函SetIntArrayRegion,本地代碼可以通過這個(gè)函數(shù)來修改所有基本數(shù)據(jù)類型數(shù)組的元素。

另外JNI還提供一系列直接獲取數(shù)組元素指針的函數(shù)Get/Release<Type>ArrayElements,比如:GetIntArrayElements、ReleaseArrayElements、GetFloatArrayElements、ReleaseFloatArrayElements等。下面我們用這種方式重新實(shí)現(xiàn)計(jì)算數(shù)組元素的和。

JNIEXPORT jint JNICALL Java_IntArray_sumArray
(JNIEnv *env, jobject obj, jintArray j_array)
{
    jint i, sum = 0;
    jint *c_array;
    jint arr_len;
    // 可能數(shù)組中的元素在內(nèi)存中是不連續(xù)的,JVM可能會(huì)復(fù)制所有原始數(shù)據(jù)到緩沖區(qū),然后返回這個(gè)緩沖區(qū)的指針
    c_array = (*env)->GetIntArrayElements(env,j_array,NULL);
    if (c_array == NULL) {
        return 0;   // JVM復(fù)制原始數(shù)據(jù)到緩沖區(qū)失敗
    }
    arr_len = (*env)->GetArrayLength(env,j_array);
    printf("arr_len = %d\n", arr_len);
    for (i = 0; i < arr_len; i++) {
        sum += c_array[i];
    }
    (*env)->ReleaseIntArrayElements(env,j_array, c_array, 0); // 釋放可能復(fù)制的緩沖區(qū)
    return sum;
}

GetIntArrayElements第三個(gè)參數(shù)表示返回的數(shù)組指針是原始數(shù)組,還是拷貝原始數(shù)據(jù)到臨時(shí)緩沖區(qū)的指針,如果是JNI_TRUE:表示臨時(shí)緩沖區(qū)數(shù)組指針,JNI_FALSE:表示臨時(shí)原始數(shù)組指針。開發(fā)當(dāng)中,我們并不關(guān)心它從哪里返回的數(shù)組指針,這個(gè)參數(shù)填NULL即可,但在獲取到的指針必須做校驗(yàn),因?yàn)楫?dāng)原始數(shù)據(jù)在內(nèi)存當(dāng)中不是連續(xù)存放的情況下,JVM會(huì)復(fù)制所有原始數(shù)據(jù)到一個(gè)臨時(shí)緩沖區(qū),并返回這個(gè)臨時(shí)緩沖區(qū)的指針。有可能在申請(qǐng)開辟臨時(shí)緩沖區(qū)內(nèi)存空間時(shí),會(huì)內(nèi)存不足導(dǎo)致申請(qǐng)失敗,這時(shí)會(huì)返回NULL。

在Java中創(chuàng)建的對(duì)象全都由GC(垃圾回收器)自動(dòng)回收,不需要像C/C++一樣需要程序員自己管理內(nèi)存。GC會(huì)實(shí)時(shí)掃描所有創(chuàng)建的對(duì)象是否還有引用,如果沒有引用則會(huì)立即清理掉。當(dāng)我們創(chuàng)建一個(gè)像int數(shù)組對(duì)象的時(shí)候,當(dāng)我們?cè)诒镜卮a想去訪問時(shí),發(fā)現(xiàn)這個(gè)對(duì)象正被GC線程占用了,這時(shí)本地代碼會(huì)一直處于阻塞狀態(tài),直到等待GC釋放這個(gè)對(duì)象的鎖之后才能繼續(xù)訪問。為了避免這種現(xiàn)象的發(fā)生,JNI提供了Get/ReleasePrimitiveArrayCritical這對(duì)函數(shù),本地代碼在訪問數(shù)組對(duì)象時(shí)會(huì)暫停GC線程。不過使用這對(duì)函數(shù)也有個(gè)限制,在Get/ReleasePrimitiveArrayCritical這兩個(gè)函數(shù)期間不能調(diào)用任何會(huì)讓線程阻塞或等待JVM中其它線程的本地函數(shù)或JNI函數(shù),和處理字符串的Get/ReleaseStringCritical函數(shù)限制一樣。這對(duì)函數(shù)和GetIntArrayElements函數(shù)一樣,返回的是數(shù)組元素的指針。

JNIEXPORT jint JNICALL Java_IntArray_sumArray
(JNIEnv *env, jobject obj, jintArray j_array)
{
    jint i, sum = 0;
    jint *c_array;
    jint arr_len;
    jboolean isCopy;
    c_array = (*env)->GetPrimitiveArrayCritical(env,j_array,&isCopy);
    printf("isCopy: %d \n", isCopy);
    if (c_array == NULL) {
        return 0;
    }
    arr_len = (*env)->GetArrayLength(env,j_array);
    printf("arr_len = %d\n", arr_len);
    for (i = 0; i < arr_len; i++) {
        sum += c_array[i];
    }
    (*env)->ReleasePrimitiveArrayCritical(env, j_array, c_array, 0);
    return sum;
}

2.8、總結(jié)

1、對(duì)于小量的、固定大小的數(shù)組,應(yīng)該選擇Get/SetArrayRegion函數(shù)來操作數(shù)組元素是效率最高的。因?yàn)檫@對(duì)函數(shù)要求提前分配一個(gè)C臨時(shí)緩沖區(qū)來存儲(chǔ)數(shù)組元素,可以直接在Stack(棧)上或用malloc在堆上來動(dòng)態(tài)申請(qǐng),當(dāng)然在棧上申請(qǐng)是最快的。也許會(huì)有人有疑問,訪問數(shù)組元素還需要將原始數(shù)據(jù)全部拷貝一份到臨時(shí)緩沖區(qū)才能訪問而覺得效率低?其實(shí)像這種復(fù)制少量數(shù)組元素的代價(jià)是很小的,幾乎可以忽略。這對(duì)函數(shù)的另外一個(gè)優(yōu)點(diǎn)就是,允許傳入一個(gè)開始索引和長(zhǎng)度來實(shí)現(xiàn)對(duì)子數(shù)組元素的訪問和操作(SetArrayRegion函數(shù)可以修改數(shù)組),不過傳入的索引和長(zhǎng)度不要越界,函數(shù)會(huì)進(jìn)行檢查,如果越界了會(huì)拋出ArrayIndexOutOfBoundsException異常。

2、如果不想預(yù)先分配C緩沖區(qū),并且原始數(shù)組長(zhǎng)度也不確定,而本地代碼又不想在獲取數(shù)組元素指針時(shí)被阻塞的話,使用Get/ReleasePrimitiveArrayCritical函數(shù)對(duì),就像Get/ReleaseStringCritical函數(shù)對(duì)一樣,使用這對(duì)函數(shù)要非常小心,以免死鎖。

3、Get/Release<type>ArrayElements系列函數(shù)永遠(yuǎn)是安全的,JVM會(huì)選擇性的返回一個(gè)指針,這個(gè)指針可能指向原始數(shù)據(jù),也可能指向原始數(shù)據(jù)的復(fù)制。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容