JNI基本類型數組操做

JNI函數動態註冊進階一文可知,JNI把Java類型分爲兩類來處理,一類是基本類型,另外一類是引用類型,並且引用類型都是用jobject類型表示的,以下圖所示java

引用類型

從這個圖中能夠看出,引用類型中有一種比較特殊的類型--數組,在JNI層中有好多個類型相對應,例如int[]對應於jintArrayString[]對應於jobjectArrayc++

從上圖中其實還能夠看到的一點是,JNI中對數組的處理也分爲基本類型和引用類型。因爲篇幅關係,本文只講述JNI中基本類型數組的操做,下一篇文章講述引用類型數組的操做。數組

例子

如今假設有一個Java類的native方法緩存

public class ArrayTest {
    static {
        System.loadLibrary("array_jni");
    }

    public native void handleArray(int[] a);
    
    public static void main(String[] args) {
        int[] a = {1, 2, 3};
        new ArrayTest().handleArray(a);
    }
}
複製代碼

而且再次假設在JNI層的實現函數以下bash

static void com_uni_ndkdemo_ArrayTest_handleArray(JNIEnv *env, jobject *thiz, jintArray array) {

}
複製代碼

若是不瞭解JNI函數是如何註冊的,能夠參考不使用IDE作一次JNI開發, JNI函數動態註冊JNI函數動態註冊進階函數

com_uni_ndkdemo_ArrayTest_handleArray中將會實現對基本類型數組的操做。post

Get<PrimitiveType>ArrayElements

Get<PrimitiveType>ArrayElements函數返回一個基本類型的數組,其中<PrimitiveType>表示基本類型,例爲GetIntArrayElements是獲取int類型數組。ui

NativeType *Get<PrimitiveType>ArrayElements(JNIEnv *env,
                        ArrayType array, jboolean *isCopy);
複製代碼

首先解釋下參數spa

  • ArrayType array: Java的數組對象
  • jboolean *iscopy: 若是isCopy不爲NULL,當返回指針指向的是原數組的拷貝時,*isCopy值爲JNI_TRUE,若是返回的指針指向原數組,那麼*isCopy的值爲JNI_FALSE

這個通用函數有兩個類型須要替換,一個是返回類型NativeType表示一個JNI類型,一個是ArrayType表示JNI的數組類型。怎麼替換呢?舉個例子吧,若是ArrayType array是Java的int[]類型對象,那麼ArrayType就是Javaint[]類型在JNI中的對應類型,也就是jintArray。函數的返回值是一個指針,很顯然int[]在JNI中確定是一個jint的指針,所以NativeType就是jint。因此,對於Java的int[]對象,函數原型以下線程

jint * GetIntArrayElements(JNIEnv *env, jintArray array, jboolean *isCopy);
複製代碼

GetIntArrayElements 返回一個jint類型的指針(指針可能爲NULL),可能指向Java的原始數組,也可能指向一個拷貝的數組,這個取決於虛擬機,可是咱們能夠經過最後一個參數的值來判斷是否有拷貝發生。

若是函數返回的指針指向原數組,那麼全部的修改都會在原數組上進行,這與Java的方式是一致的。而若是函數返回的指針指向原始數組的拷貝,那麼全部的修改都僅僅是在拷貝上進行的,原始數組不受影響。

然而是否發生拷貝並非咱們能決定的,咱們能決定的是,當拷貝發生了,咱們可以確保全部在拷貝上的修改都能寫回到原始數組上,這就是Release<PrimitiveType>ArrayElements函數的做用。

Release<PrimitiveType>ArrayElements

void Release<PrimitiveType>ArrayElements(JNIEnv *env,
                ArrayType array, NativeType *elems, jint mode);
複製代碼

參數

  • ArrayType array: Java數組對象
  • NativeType * elems: 指向JNI類型的指針,是經過Get<PrimitiveType>ArrayElements函數獲取的
  • jint mode的值有如下幾種
    • 0 : 把拷貝數組的修改寫回原數組,並釋放NativeType *elems緩存。
    • JNI_COMMIT: 把拷貝數組的修改寫回原數組,可是並不釋放NativeType *elems緩存。
    • JNI_ABORT: 釋放NativeType *elems緩存,但並不把拷貝數組的修改寫回原數組。

關於mode參數,若是Get<PrimitiveType>ArrayElements函數不發生拷貝,mode參數就沒有任何影響。

Release<PrimitiveType>ArrayElements函數實際上是通知虛擬機NativeType *elems 指向的數組不會再被訪問,虛擬機會根據參數mode的值決定是否釋放本地數組,以及是否把修改寫回到原數組。

Release<PrimitiveType>ArrayElements是一個通用的寫法,舉個具體的例子吧,當處理的是Java的int[]對象時,函數原型以下

void ReleaseIntArrayElements(JNIEnv *env,
                jintArray array, jint *elems, jint mode);
複製代碼

讀者可根據前面所講的類型替換原理來理解這裏的類型替換,後面遇到這樣的類型替換也只會給出某個具體的例子,並不會講解如何替換。

實戰

static void com_uni_ndkdemo_ArrayTest_handleArray(JNIEnv *env, jobject *thiz, jintArray array)
{
    
    // 1. 獲取數組的長度
    jsize length = env->GetArrayLength(intArr);
    // 2. 獲取本地數組
    jint *native_int_array = env->GetIntArrayElements(intArr, NULL);
    // 3. 操做本地數組
    for (int i = 0; i < length; i++)
    {
        native_int_array[i] += 100;
    }
    // 4. 釋放本地數組
    env->ReleaseIntArrayElements(intArr, native_int_array, 0);
}
複製代碼

調用GetIntArrayElements函數的時候,第二個參數傳入了NULL,所以後面沒法判斷是否生成了數組的拷貝,然而在這裏並不關心這個問題,由於在調用ReleaseIntArrayElements函數的時候,第三個參數的值爲0,修改必定會應用到原始數組上。

調用GetIntArrayElements函數返回的本地數組,在函數返回前也沒有進行手動釋放,這是由於調用ReleaseIntArrayElements傳入的第三個參數爲0,本地數組會被自動釋放。

Get<PrimitiveType>ArrayRegion

void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array,
                                jsize start, jsize len, NativeType *buf);
複製代碼

參數

  • ArrayType array: Java數組
  • jsize start: 起始數組的索引
  • jsize len: 須要拷貝的長度
  • NativeType * buf: 本地緩衝區

Get<PrimitiveType>ArrayRegion 函數的效果是拷貝數組ArrayType array的一部分到緩存NativeType *buf中,這一部分的起始位置是start,長度爲len

若是處理的是int[]類型,對應函數原型以下

void GetIntArrayRegion(JNIEnv *env, jintArray array, jsize start,
                        jsize len, jint *buf);
複製代碼

咱們應該注意到了,Get<PrimitiveType>ArrayRegion函數的效果是拷貝,那麼天然而然有一個問題擺在眼前,那就是若是你關心拷貝數組上的修改須要寫會到原始數組上,能夠調用Set<PrimitiveType>ArrayRegion,而若是你並不關心這個問題,可能什麼也不用作(若是本地緩存是動態分配的,須要手動釋放)。

Set<PrimitiveType>ArrayRegion

void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array,
            jsize start, jsize len, const NativeType *buf);
複製代碼

參數

  • ArrayType array: Java數組對象
  • jsize start: 拷貝的起始索引
  • jsize len: 拷貝的長度
  • NativeType *buf: 拷貝的數據源

Set<PrimitiveType>ArrayRegion把緩存buf寫回到原數組array中,起始位置爲start,長度爲len

參數startlen都是對於原是數組array來講的。

實戰

static void com_uni_ndkdemo_ArrayTest_handleArray(JNIEnv *env, jobject *thiz, jintArray array)
{
    // 獲取數組長度
    const jsize length = env->GetArrayLength(array);
    // 在棧上建立緩衝區
    jint buff[length - 1];
    // 獲取原始數組最後length -1個數組的拷貝
    env->GetIntArrayRegion(array, 1, length - 1, buff);
    // 修改拷貝數組
    for (int i = 0; i < length - 1; ++i)
    {
        buff[i] += 100;
    }
    // 把拷貝數組的修改寫會到原始數組中
    env->SetIntArrayRegion(array, 1, length - 1, buff);
}
複製代碼

在這裏例子中,緩衝區jint buff[length - 1]是在棧上建立,所以在函數返回後會自動釋放內存,而若是在堆上建立緩衝區,例如使用malloc函數,那麼在函數返回前須要手動釋放內存,不然會形成內存泄露(C語言基本認知)。

當使用SetIntArrayRegion把修改寫回到原是數組後,在Java層是能夠看到數組的改變的,你們能夠本身打印看看,我這裏就不演示了。

GetPrimitiveArrayCritical & ReleasePrimitiveArrayCritical

void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);
void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);
複製代碼

這兩個函數與Get/Release<primitivetype>ArrayElements函數的使用方式是同樣的,虛擬機可能返回一個指向原數組的指針,也可能返回一個指向拷貝數組的指針。

可是這兩個函數在使用的時候有很是大的限制。這兩個函數是要成對出現的,並且在調用這兩個函數之間,不能調用可能致使當前線程阻塞而且等待其它線程的JNI函數或系統調用。可是這種限制也帶來一些好處,它使本地代碼更容易獲取一個非拷貝版本的數組的指針,虛擬機也可能暫時禁用垃圾回收功能。禁用垃圾回收功能可讓本地代碼執行更快,畢竟不會暫停本地線程,可是呢,短暫禁用垃圾回收功能對系統垃圾回收功能形成影響,能夠說利與弊相輔相成。

在實際中使用這兩個函數,須要斟酌斟酌,權衡利弊。

實戰

static jlong com_uni_ndkdemo_ArrayTest_handleArray(JNIEnv *env, jobject thiz, jintArray array) {
    // 1. 獲取數組的大小 
    jsize length = env->GetArrayLength(array);
    // 2. 獲取數組的指針
    jint * a = (jint *) env->GetPrimitiveArrayCritical(array, NULL);
    if (a == NULL) {
        return 0;
    }
    // 3. 操做數組(不要進行耗時的JNI函數調用或者系統調用)
    for (int i = 0; i < length; i++) {
        a[i] += 100;
    }
    // 4. 釋放本地數組,修改寫回原始數組
    env->ReleasePrimitiveArrayCritical(array, a, 0);
}
複製代碼

使用Get<Primitive>ArrayCriticalRelease<Primitive>ArrayCritical這兩個函數的之間,注意不要調用耗時的JNI函數或者系統調用,由於畢竟虛擬機有可能暫時禁用垃圾回收功能,若是進行耗時操做,就可能影響垃圾回收功能。

New<PrimitiveType>Array

前面的例子都是在JNI層處理Java層傳過來的基本類型數組,也能夠在JNI層建立基本類型數組並返回給Java層,使用的就是New<PrimitiveType>Array函數

ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);
複製代碼

例如,若是要返回一個int[]對象給Java層,那麼New<PrimitiveType>Array函數原型以下

jintArray NewIntArray(JNIEnv *env, jsize length);
複製代碼

那麼,在建立完Java基本類型數組後,如何給每一個元素賦值呢?固然就是用前面講過的函數。

實戰

假設Java類有一個返回int[]的方法

public class ArrayTest {
    static {
        System.loadLibrary("array_jni");
    }
    
    public native int[] getNativeArray();
}
複製代碼

通過動態註冊後,在JNI層的實現函數以下

static jintArray com_uni_ndkdemo_ArrayTest_getNativeArray(JNIEnv * env, jobject thiz) {
    // 1. 建立一個Java的int[]
    jintArray array = env->NewIntArray(2);
    // 2. 獲取數組指針
    jint *c_array = env->GetIntArrayElements(array, NULL);
    // 3. 操做數組元素
    c_array[0] = 110;
    c_array[1] = 120;
    // 4. 把修改寫回原數組而且釋放本地數組
    env->ReleaseIntArrayElements(array, c_array, 0);
    return array;
}
複製代碼

最關鍵的一步就是第一步,建立一個Java層的基本類型數組,剩下的事情就是在JNI層處理這個數組,很顯然這個事情前面都講過。

總結

在操做基本類型數組方面呢,有三種方式,Get/Release<PrimitiveType>ArrayElementsGet/Set<PrimitiveType>ArrayRegion 以及 Get/Release<Primitive>ArrayCritical。它們各有千秋,這裏就總結下。

若是操做整個數組,而且要把修改引用到原數組,那麼Get/Release<PrimitiveType>ArrayElements是首選,若是更須要執行速度,那麼能夠選擇Get/Release<Primitive>ArrayCritical

若是不須要把修改應用到原數組上,那麼Get/Set<PrimitiveType>ArrayRegion是首選,畢竟Get/Release<PrimitiveType>ArrayElementsGet/Release<Primitive>ArrayCritical對因而否生成數組的拷貝具備不肯定性,須要加入代碼進行判斷。

預告

後面的文章就會講解JNI的關於引用類型數組的操做,這個稍微就有點複雜,若是引用類型是字符串類型,那就又是另一種說法了。不過在此以前,但願你們好好理解我前面寫的文章,由於這是一環套一環的,基本功丟了,就無從談起上層建築了。

相關文章
相關標籤/搜索