Java学习之五:NewDirectByteBuffer和Java.nio.ByteBuffer

android----在JNI中如何使用ByteBuffer

1,ByteBuffer 定义

在NIO中,数据的读写操作始终是与缓冲区相关联的(读取时信道(SocketChannel)将数据读入缓冲区,写入时首先要将发送的数据按顺序填入缓冲区)
缓冲区是定长的,基本上它只是一个列表,它的所有元素都是基本数据类型。ByteBuffer是最常用的缓冲区,它提供了读写其他数据类型的方法,且信道的读写方法只接收ByteBuffer。
ByteBuffer有以下几种常见属性:
mark:初始值为-1,标记索引地点;
position:初始值为0,索引下标;
limit:最好定义成bytebuffer的长度,即允许可读空间长度;
capacity:缓冲区能容纳的数据元素的最大数量,创建之后无法被改变;

2,ByteBuffer使用

2.1创建ByteBuffer

2.1.1使用allocate()创建:

ByteBuffer buf = ByteBuffer.allocate(length);    //length表示buf的长度

2.1.2使用数组创建:

ByteBuffer buf = ByteBuffer.wrap(byteArray);   //byteArray表示一个数组

2.2回绕缓冲区

buf.flip();  //这个方法用来将缓冲区准备为数据传出状态,执行以上方法后,输出通道会从数据的开头而不是末尾开始.回绕保持缓冲区中的数据不变,只是准备写入而不是读取。

2.3清除缓冲区

buf.clear();  //这个方法实际上也不会改变缓冲区的数据,而只是简单的重置了缓冲区的主要索引值.不必为了每次读写都创建新的缓冲区,那样做会降低性能.相反,要重用现在的缓冲区,在再次读取之前要清除缓冲区。

2.4 ByteBuffer与byte[]交互

byte[] bytearray = new byte[10];

ByteBuffer buf = ByteBuffer.wrap(bytearray);//将数组写入buf

bytearray = new byte[buf.remaining()];

buf.get(bytearray,0,bytearray.length());//将数据读到数组中

bytearray = new byte[buf.capacity()];

3 ByteBuffer与JNI交互


 在Java1.4版本中引入的JNI有三个函数可以用于NIO的直接缓冲器。一个直接字节缓冲器是一个用于字节数据的容器,Java将尽力在它上面执行本机I/O操作。JNI定义了三个用于NIO操作的函数。

/*     基于到存储器地址的指针以及存储器长度(容量),函数分配并且返回一个新的Java.nio.ByteBuffer。如果函数没有针对当前Java虚拟机实现,则返回NULL,或者抛出一个异常。如果没有存储器可用,则将会抛出一个OutOfMemoryException。*/
jobject NewDirectByteBuffer(void* address, jlong capacity);
/*   GetDirectBufferAddress函数返回一个指向被传入的java.nio.ByteBuffer对象的地址指针。如果函数尚未针对当前虚拟机实现,或者如果buf不是java.nio.ByteBuffer的一个对象,又或者存储器区尚未定义,则都将返回NULL。*/
void* GetDirectBufferAddress(jobject buf);
/*GetDirectBufferCapacity函数返回被传入的java.nio.ByteBuffer对象的容量(以字节计数)。如果函数没有针对当前环境实现,或者如果buf不是java.nio.ByteBuffer类型的对象返回-1。*/
jlong GetDirectBufferCapacity(jobject buf);
3.1Jni中调用
Java层:
 public final int processData(ByteBuffer data);
Native 接口:
 private native long native_Process(ByteBuffer data);
Jni层:
static jlong native_Process(JNIEnv *env, jobject obj,jobject data);
注意ByteBuffer在JNI层中的签名:Ljava/nio/ByteBuffer;
 
eg(C++):
jclass cls = env->GetObjectClass(obj);
jfieldID fid = env->GetFieldID(cls, "data", "Ljava/nio/ByteBuffer;");
jobject bar = env->GetObjectField(obj, fid);
pImageData->data= (MByte*)env->GetDirectBufferAddress(bar);
//data是结构体pImageData中的byte[];

 

 

ByteBuffer使用总结
一、概述
二、DirectByteBuffer的使用方式
1.native创建DirectByteBuffer,java通过native映射
2.java层创建buffer数组,native层获取对应的native buffer
3.两种方式的共同点
三、总结
一、概述
最近做一个项目,性能不达标,在软件实现中频繁的调用jni实现java、native的数据传递,后来把数据直接放在native,减少native的调用,性能优化明显。查了下java、native的内存共享,因此,了解到java的java.nio包。在项目中主要使用的是ByteBuffer,其他类感兴趣的自行查询。

ByteBuffer内存共享分为两种:1、java栈内存共享 2、native堆申请内存。
我这里使用的是第二种方式,也即DirectByteBuffer。第一种,我的理解是java层,不同线程间方便 内存共享。由于native和java的内存不在 一个空间,为了共享,必须为一个空间,在native申请内存,IO性能 会更好,也即DirectByteBuffer。

二、DirectByteBuffer的使用方式
1.native创建DirectByteBuffer,java通过native映射
优点:封装实现
缺点:ByteBuffer一些接口不可以使用,如array(),获取数据需要调用jni接口进行拷贝
代码实现:

status_t JNJBuffer::createByteBufferFromNJBuffer(
        JNIEnv *env, bool readOnly, bool clearBuffer, const sp<NJBuffer> &buffer,
        jobject *buf) const {
    // if this is an NJBuffer that doesn't actually hold any accessible memory,
    // use a null ByteBuffer
    *buf = NULL;

    if (buffer == NULL) {
        ALOGV("createByteBufferFromABuffer - given NULL, returning NULL");
        return OK;
    }

    if (buffer->base() == NULL) {
        ALOGV("createByteBufferFromABuffer ");
        return OK;
    }

    jobject byteBuffer =
        env->NewDirectByteBuffer(buffer->base(), buffer->capacity());
    ALOGD("--------readOnly = %d",readOnly);
    if (readOnly && byteBuffer != NULL) {
        jobject readOnlyBuffer = env->CallObjectMethod(
                byteBuffer, mByteBufferAsReadOnlyBufferMethodID);
        env->DeleteLocalRef(byteBuffer);
        byteBuffer = readOnlyBuffer;
    }
    if (byteBuffer == NULL) {
        return NO_MEMORY;
    }
    jobject me = env->CallObjectMethod(
            byteBuffer, mByteBufferOrderMethodID, mNativeByteOrderObj);
    env->DeleteLocalRef(me);
    me = env->CallObjectMethod(
            byteBuffer, mByteBufferLimitMethodID,
            clearBuffer ? buffer->capacity() : (buffer->offset() + buffer->size()));
    env->DeleteLocalRef(me);
    me = env->CallObjectMethod(
            byteBuffer, mByteBufferPositionMethodID,
            clearBuffer ? 0 : buffer->offset());
    env->DeleteLocalRef(me);
    me = NULL;

    *buf = byteBuffer;
    return OK;
}
status_t JNJBuffer::getBuffers(
        JNIEnv *env, bool input, sp<NJBuffer> buffers[], size_t bufferSize, jobjectArray *bufArray) const {
    if(buffers == NULL || bufferSize <= 0 || bufArray == NULL) {
        return BAD_VALUE;
    }
    *bufArray = (jobjectArray)env->NewObjectArray(
            bufferSize, mByteBufferClass, NULL);
    if (*bufArray == NULL) {
        return NO_MEMORY;
    }

    for (size_t i = 0; i < bufferSize; ++i) {
        const sp<NJBuffer> &buffer = buffers[i];

        jobject byteBuffer = NULL;
        status_t err = createByteBufferFromNJBuffer(
                env, false /* readOnly */, true /* clearBuffer */, buffer, &byteBuffer);
        if (err != OK) {
            return err;
        }
        if (byteBuffer != NULL) {
            env->SetObjectArrayElement(
                    *bufArray, i, byteBuffer);

            env->DeleteLocalRef(byteBuffer);
            byteBuffer = NULL;
        }
    }

    return OK;
}
java层调用getBuffers,获取java层映射的ByteBuffers数组,通过bufArray返回。
在java层,通过ByteBuffer的get、put等函数实现对buffer的操作。如果bu

2.java层创建buffer数组,native层获取对应的native buffer
优点:可以使用array,减少jni的调用。
缺点:buffer分配在java层,buffer大小可控性差,容易被开发修改
代码实现:

static jboolean com_icetech_randis_NetLinuxCamera_setBuffers(
        JNIEnv *env, jobject thiz, jint type, jobjectArray byteBuffers, jint size) {
    ALOGD("com_icetech_randis_NetLinuxCamera_setBuffers");
    sp<NetLinuxCamera> nlcdev = getNetLinuxCamera(env, thiz);
    if (nlcdev == NULL) {
        jniThrowException(env, "java/lang/NullPointerException", NULL);
        return false;
    }

    for(int i = 0;i < size;i++) {
        jobject byteBuffer = env->GetObjectArrayElement(byteBuffers, i);
        if(byteBuffer == NULL) {
            jniThrowException(env, "java/lang/NullPointerException", NULL);
            return false;
        }
        // 以下两行是重点,获取java buffer的native指针首地址及buffer的长度
        // 获取buffer首地址后就可以操作buffer,读写数据
        void* buffer  =env->GetDirectBufferAddress(byteBuffer);
        int capacity = env->GetDirectBufferCapacity(byteBuffer);
        // 此处不用关心
        status_t err = nlcdev->initBuffer(type, i, buffer, capacity);
        if(err != OK) {
            return false;
        }
    }

    return true;
}
注意: 该方式在java层可以使用array(),但不能直接使用,需要配合arrayoffset()。在创建buffer时,为了内存地址对齐,有一定的偏移。
使用方式:

byte[] array = mCacheBuffers[currentIndex].array();
offset = mCacheBuffers[currentIndex].arrayOffset();
// 有效数据从offset开始
mCb.onVideoBufferAvailable(array, offset, length);

3.两种方式的共同点
若buffer作为循环buffer,native层要自己实现对buffer的管理;在java层,get、put后,position会自动移动,具体如何移动,没有用到没有深究。
若buffer不做为循环buffer,每次数据写完后需要执行buffer.clear(),该函数不会影响buffer中数据,只是把读写数据的位置归零,也不影响offset。

三、总结
之前开发写代码较少,一直想写代码,在写代码是思路较清晰,总是不能下手,比如使用java、native内存共享,使用arry()读取数据,java、native层数据迁移不调用jni接口,还有刚开始使用array()接口不能拿到正确数据,网上没有查到资料,需要写demo研究下,一拖再拖。真正去实现了,其实也挺简单的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

隨意的風

如果你觉得有帮助,期待你的打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值