在Java和C之间发送int[]数组

14
我在Android的Java代码中有一些图像处理代码,作用于两个大的int数组。大多数情况下,Java足够快,但我需要使用JNI和NDK中的C来加速一些操作。
我所知道唯一的方法是使用ByteBuffer.allocateDirect创建一个新缓冲区,将数据复制到其中,然后让C代码对缓冲区进行操作。但是,我无法想到任何办法在Java中操作此缓冲区,就好像缓冲区是int[]或byte[]一样。例如,对newly created buffer调用ByteBuffer.array()会失败。有没有办法让它起作用?
我内存有限,想要减少所需的数组/缓冲区数量。例如,如果我可以使用IntBuffer.wrap(new int[...])创建缓冲区,然后直接在Java中操作支持缓冲区的数组,那将很不错,但是我无法这样做,因为在这里对于JNI似乎唯一有效的是ByteBuffer.allocateDirect。
是否有其他方式在C和Java之间发送数据?我能否在C端分配内存,让Java直接向那里发送数据?
编辑:比较缓冲区使用和int[]使用的基准测试:
int size = 1000;
IntBuffer allocateDirect = java.nio.ByteBuffer.allocateDirect(4 * size).asIntBuffer();
for (int i = 0; i < 100; ++i)
{
  for (int x = 0; x < size; ++x)
  {
    int v = allocateDirect.get(x);
    allocateDirect.put(x, v + 1);
  }
}

int[] intArray = new int[size];
for (int i = 0; i < 100; ++i)
{
  for (int x = 0; x < size; ++x)
  {
    int v = intArray[x];
    intArray[x] = v + 1;
  }
}

在Droid手机上,缓冲区版本需要约10秒才能完成,而数组版本只需要约0.01秒。


1
等等,ByteBuffer.asIntBuffer有什么问题吗?你真的需要一个int[]吗? - ephemient
而且,如果你想的话,可以在IntBuffer上调用.array()。 - mpontillo
Java中的.array()对于直接分配的ByteBuffer和IntBuffer数组都会抛出Unsupported异常。我真的想访问int[],以便可以在Java中执行一些不那么密集的图像处理工作,例如通过检查哪些像素在亮度阈值以上/以下将图像转换为黑白。在Android中对每个像素使用IntBuffer的get/put操作速度太慢,并且很笨拙。 - rbcc
@rebeccaT,将你的代码改为使用IntBuffer而不是int[],并使用直接缓冲区。 - bestsss
2
@rebeccaT,如果put/get方法如此缓慢,那么肯定出了什么问题。这些方法应该被内联并编译为非常简单的CPU指令,而不需要任何虚拟方法调用。是的,我建议您放弃int[]并始终使用直接IntBuffer。 - bestsss
显示剩余8条评论
3个回答

17

http://java.sun.com/docs/books/jni/html/objtypes.html,使用JNI的Get/Release<TYPE>ArrayElements(...)

在这个例子中,我会传递一个数组(为了方便起见,假设它是int array = new int[10]),然后用0-9填充它。

 JNIEXPORT jint JNICALL 
 Java_IntArray_doStuffArray(JNIEnv *env, jobject obj, jintArray arr)
 {

     // initializations, declarations, etc
     jint *c_array;
     jint i = 0;

     // get a pointer to the array
     c_array = (*env)->GetIntArrayElements(env, arr, NULL);

     // do some exception checking
     if (c_array == NULL) {
         return -1; /* exception occurred */
     }

     // do stuff to the array
     for (i=0; i<10; i++) {
         c_array[i] = i;
     }

     // release the memory so java can have it again
     (*env)->ReleaseIntArrayElements(env, arr, c_array, 0);

     // return something, or not.. it's up to you
     return 0;
 }

请参考第3.3节,尤其是3.3.2 -- 这将允许您获取指向Java内存中的数组的指针,在本地代码中修改它,并释放它,从而允许您修改数组。

我刚在自己的项目中使用了它(使用短数组),效果非常好 :)


谢谢,这段代码帮助我连接到SigLib库中的FIR滤波器方法。 - JPM
2
获取数组长度 int arrayLength = (*env)->GetArrayLength(env, arr); - Ahmed Hegazy
当我尝试这样做时,Java doStuffArray(arr) 在本地的 Java_IntArray_doStuffArray 完成之前就返回了。我需要做什么才能阻止 Java 端直到本地端完成? - Kevin S. Miller

5
如果您正在使用直接分配的缓冲区,则可以使用GetDirectBufferAddress函数直接从C中访问后备数组,从而避免了复制区域的可能性。
您可以像操作普通C数组一样直接操作返回的地址,并且它将直接修改Java直接分配的缓冲区。
然后,如ephemient所述,您可以使用ByteBuffer.asIntBuffer()和其它方法以模拟各种Java基元类型的数组方式访问缓冲区。 http://download.oracle.com/javase/1.4.2/docs/guide/jni/jni-14.html

4

您可以使用回调函数将数据从本地层发送到Java。

在Java层:在我的本地类中,我有以下方法:

//Native method
public native String getStrData(int size);

//Callback method
public void addData(char[] native_data, int size) {

    ...

}

在本地层:在我的本地实现中:

JNIEXPORT jstring JNICALL Java_com_pkg_NativeClass_getStrData 
   (JNIEnv *env, jobject obj, jint size) {
     ...

     jclass native_class;           /* Callback: native class */
     jmethodID native_method_id;    /* Callback: native method id */
     jcharArray row;                /* Callback: native data */

     ...

    /* Start Callback: Native to Java  */   
    native_class = (*env)->GetObjectClass(env, obj);
    native_method_id = (*env)->GetMethodID(env, native_class, "addData", "([CI)V");
    if (native_method_id == 0) {
        return (jstring)ERR_NATIVE_METHODID;
    }
    row = (jcharArray)(*env)->NewCharArray(env, size);
    /* jc has the data to be sent to Java */
    (*env)->SetCharArrayRegion(env, (jcharArray)row, (jsize)0, size, (jchar *)jc);

    (*env)->CallVoidMethod(env, obj, native_method_id, row, size);
    /* End Callback */

     ...
}

请问,jc是什么类型?它是char* jc吗? - dma_k
在我的情况下,'jc'是一个已知大小的数组。 - TheCottonSilk
你能给一个提示吗,如何操作jc(例如设置字符数据)?我在这里和那里寻找使用SetCharArrayRegion()的示例,但找不到如何创建/初始化jchar数组的完整示例(例如从ASCII 1字节字符串char*)。谢谢。 - dma_k

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接