本篇結(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ù)制。