Android NDK开发(八):JNI函数接口详解—字符串处理

         java中的字符串默认是UTF-16编码,C/C++中的字符串默认是UTF-8编码,这就需要JNI提供一套具有字符串转换等功能的接口。
(注意:其中UTF是Unicode Transformation Format的缩写,UTF-8和UTF-16都属于unicode。)

1 涉及到的JNI接口

        一共涉及到12个接口,根据操作字符串/字符数组的编码格式不同可分为两部分:

UTF-8编码的UTF-16编码的
本地字符数组转为java字符串NewStringUTFNewString
获取java字符串在相应编码下的字符数组长度GetStringUTFLengthGetStringLength
java字符串转为本地字符数组GetStringUTFCharsGetStringChars
释放本地字符数组ReleaseStringUTFCharsReleaseStringChars
将java字符串拷贝到本地字符数组GetStringUTFRegionGetStringRegion
将java字符串转为本地UTF-16字符数组GetStringCritical
释放本地UTF-16字符数组ReleaseStringCritical

        其中带UTF的接口用于处理UTF-8字符串,不带UTF的接口用于处理UTF-16字符串。

2 接口详解

/**
 * 作用:将本地UTF-8字符数组转换为java字符串
 * @param bytes 本地不可变的UTF-8字符数组
 * @return java字符串
 */
jstring NewStringUTF(const char* bytes);

/**
 * 作用:获取java字符串UTF-8编码长度,即转为UTF-8编码之后,有多少个UTF-8编码
 * @param string java字符串
 * @return java字符串长度
 *
 * 注意:jsize 是 jint 类型的别名,其定义为:typedef jint jsize。
 */
jsize GetStringUTFLength(jstring string);

/**
 * 作用:获取java字符串对应的UTF-8编码的本地不可变字符数组
 * @param string java字符串
 * @param isCopy 用于结果说明,是否拷贝源字符串。返回值为JNI_TRUE或JNI_FALSE,若返回JNI_TRUE,
 * 则表示将java字符串拷贝到新分配的内存空间,返回地址为新空间的地址。若返回JNI_FALSE,则表示直接返回java字符串
 * 的地址。开发过程中,若不关心这个返回值,可传入NULL。
 * @return 本地不可变的UTF-8字符数组指针
 *
 * 注意:JVM是否会拷贝原始字符串来生成本地UTF-8字符数组是不可以预测的。
 */
const char* GetStringUTFChars(jstring string, jboolean* isCopy);

/**
 * 作用:释放通过GetStringUTFChars函数获取的本地UTF-8字符数组内存
 * @param string java字符串
 * @param utf 与java字符串对应的本地不可变UTF-8字符数组
 *
 * 注意:不管通过GetStringUTFChars函数获取的UTF-8字符数组是否为拷贝得到,最好都调用
 * 该函数释放一下,若是拷贝,则释放的是UTF-8字符数组内存,若没有拷贝,则释放一个内存占用标记;
 * 调用该函数不会影响源字符串
 */
void ReleaseStringUTFChars(jstring string, const char* utf);

/**
 * 作用:将java字符串从指定位置和长度 拷贝到 本地UTF-8字符数组中
 * @param str java字符串
 * @param start 开始拷贝位置
 * @param len 拷贝长度
 * @param buf 内存空间
 */
void GetStringUTFRegion(jstring str, jsize start, jsize len, char* buf);

 /**
  * 作用:将本地的UTF-16字符数组转换为java字符串
  * @param unicodeChars 不可变的本地UTF-8字符数组
  * @param len 本地UTF-8字符数组长度
  * @return java字符串
  */
jstring NewString(const jchar* unicodeChars, jsize len);

 /**
  * 作用:获取java字符串UTF-16编码长度,即转为为UTF-16编码之后,有多少个UTF-16编码
  * @param string
  * @return
  */
jsize GetStringLength(jstring string);

/**
 * 作用:获取java字符串对应的UTF-16编码的本地不可变字符数组
 * @param string java字符串
 * @param isCopy 用于结果说明,是否拷贝源字符串。返回值为JNI_TRUE或JNI_FALSE,
 * 若返回JNI_TRUE,则表示将java字符串拷贝到新分配的内存空间,返回地址为新空间的地址。
 * 若返回JNI_FALSE,则表示直接返回java字符串的地址。开发过程中,若不关心这个返回值,可传入NULL。
 * @return 本地不可变的UTF-16字符数组指针
 *
 * 注意:JVM是否会拷贝原始字符串来生成UTF-8字符数组是不可以预测的。
 */
const jchar* GetStringChars(jstring string, jboolean* isCopy);

/**
 * 作用:释放通过GetStringChars函数获取的UTF-16字符数组内存
 * @param string java字符串
 * @param utf 与java字符串对应的不可变UTF-16字符数组
 *
 * 注意:不管通过GetStringUTFChars函数获取的UTF-16字符指针是否为复制得到,最好都调
 * 用该函数释放一下,若是拷贝,则释放的是UTF-8字符数组内存,若没有拷贝,则释放一个内存占用标记;
 * 调用该函数也不会影响源字符串
 */
void ReleaseStringChars(jstring string, const char* utf);

/**
 * 作用:将java字符串从指定位置和长度 拷贝到 本地UTF-16字符数组中
 * @param str java字符串
 * @param start 开始拷贝位置
 * @param len 拷贝长度
 * @param buf 内存空间
 */
void GetStringRegion(jstring str, jsize start, jsize len, char* buf);

/**
 * 获取java字符串对应的UTF-16编码的本地不可变字符数组,提高JVM返回源字符串直接指针的可能性
 * @param string java字符串
 * @param isCopy 返回标志,表示返回的是否为拷贝UTF-16编码的字符数组
 * @return 不可变的UTF-16字符数组指针
 */
const jchar* GetStringCritical(jstring string, jboolean* isCopy);

/**
 * 释放通过GetStringChars函数获取的UTF-16字符数组内存
 * @param string java字符串
 * @param isCopy 返回标志,表示返回的是否为拷贝UTF-16编码的字符数组
 */
void ReleaseStringCritical(jstring string, const jchar* carray);

        几点注意:
        1)带UTF的接口就是处理本地UTF-8编码的字符数组和java字符串相互转换的,不带的就是处理本地UTF-16编码的字符数组和java字符串相互转换的。
        2)GetStringUTFChars 和 ReleaseStringUTFChars 最好成对调用,不管前者是不是拷贝的,都要养成释放的习惯,否则容易内存。
        3)调用GetStringUTFChars / GetStringChars时,若本地内存空间不够,会导致调用失败,返回NULL,并抛出一个OutOfMemoryError异常。JNI的异常和Java中的异常处理流程是不一样的,Java遇到异常如果没有捕获,程序会立即停止运行。而JNI遇到未决的异常不会改变程序的运行流程,也就是程序会继续往下走,这样后面针对这个字符串的所有操作都是非常危险的,因此需要安全类型检查,配合return语句跳过后面的代码,并立即结束当前方法。

3 相似接口对比

(1)GetStringUTFChars/GetStringChars 和 GetStringUTFRegion/GetStringRegion 区别:

GetStringUTFChars/GetStringCharsGetStringUTFRegion/GetStringRegion
作用java串转本地字符数组java串转本地字符数组或部分拷贝
是否拷贝java源串可能拷贝 可能不拷贝 看cpu心情拷贝源串
释放需要调用ReleaseXXX释放

如果是栈buffer无需手动释放

如果是堆buffer需手动释放

返回的本地字符数组是否可变不可变(可通过强制类型转换变为为可变)可变
内存溢出可能内存溢出不会内存溢出,因为需要我们自己申请buffer
适用情况

大java字符串的转换

小java字符串的转

(2)GetStringChars 和 GetStringCritical的区别:

GetStringChars GetStringCritical
作用java串转本地字符数组java串转本地字符数组
是否拷贝java源串可能拷贝 可能不拷贝 可能拷贝 可能不拷贝,但提高了不拷贝的概率 
释放需要调用ReleaseXXX释放需要调用ReleaseXXX释放
是否会暂停GC不会

注意:当使用GetStringCritical/ReleaseStringCritical这对函数时,函数间的代码会被当做临界区(critical region), 在执行临界区代码时 GC会被禁用,故不要调用任何会阻塞当前线程(如 IO 之类的)或分配对象的函数,另外,也应尽量避免调用其他 JNI 方法,除带有Critical后缀的方法,包括GetStringCritical/ReleaseStringCritical,Get/ReleasePrimitiveArrayCritical。

4 接口使用

        以 UTF-8 相关接口的使用为例。

(1)java字符串 转为 本地char符数组

1)利用GetStringUTFChars

void string2Char(JNIEnv * env,jstring str){
    //1 定义一个const char数组
    const char* char_arr_const;
    //2 将java字符串转为UTF-8格式的const char数组
    char_arr_const = env->GetStringUTFChars(str, nullptr);
    //3 可选,若需要char数组,可通过强制类型转换,将const char数组转为char数组
    char* char_arr = const_cast<char *>(char_arr_const);

    //TODO:自己的处理逻辑

    //4 释放const char数组
    env->ReleaseStringUTFChars(str,char_arr_const);
}

2)利用GetStringUTFRegion

void string2Char1(JNIEnv * env,jstring str){
    //1 获取java字符串长度
    int length =  env->GetStringUTFLength(str);
    //2 在栈上分配buffer,不需要手动释放,函数结束自动释放
    char char_arr[length];
    //3 将java字符串 按照UTF-8的形式拷贝到buffer中
    env->GetStringUTFRegion(str,0,length,char_arr);

    //在自由存储区分配buffer空间。自由存储区不仅可以是堆,还可以是静态存储区
    char *p = new char[length];
    env->GetStringUTFRegion(str,0,length,p);
    //释放自由存储区空间,与new[]关键字成对使用
    delete[] p;

    //在堆分配buffer空间
    char *ptr = (char *)malloc(sizeof(char) * length);
    if(nullptr == ptr){
        return;
    }
    env->GetStringUTFRegion(str,0,length,ptr);
    //释放堆空间,与malloc函数成对使用
    free(ptr);

    //TODO:自己的处理逻辑
}

注意:本地buffer的空间分配,可通过直接定义、new关键字、malloc函数三种方式,区别如下:

直接定义new关键字malloc函数
分配空间位置

自由存储区

释放函数结束自动释放手动调用delete关键字释放手动调用free函数释放
内存分配失败本地抛出bac_alloc异常返回nullptr

上述 自由存储区 可能是堆,也可能是静态存储区,这取决于new关键字的实现细节。若有必要将buffer分配在堆上,对于JNI来说还是malloc比较好,因为空间不够时,可以返回空指针而不会报错,在jni中本地层代码执行不受jvm虚拟机的控制,因此有异常了并不会停止native函数的执行并把控制权交给异常处理程序。

(2)本地char符数组 转为 java字符串

jstring char2String(JNIEnv * env,const char *char_arr){
   return env->NewStringUTF(char_arr);
}

注意:NewStringUTF只会将char数组有效长度的部分转化为字符串,这里有一个技巧,尽量在本地将char/jchar数组转为jstring返回给java层,而不是,将jchar数组返给java层,在java层将jchar数组转为String,因为java层将jchar数组转String时,是将jchar数组的全部元素转化为String,而不是有效长度部分,无效长度部分会变成乱码!

5 总结

(1)用于字符串处理的JNI接口涉及到12个,主要分为两类:用于处理本地UTF-8字符数组和java字符串相互转换的,用于处理本地UTF-16字符数组和java字符串相互转换的,前者接口名字会带有UTF。
(2)GetStringUTFChars/GetStringChars 和 ReleaseStringUTFChars/ReleaseStringChars 要成对调用,释放资源。
(3)
GetStringUTFRegion/GetStringRegion ,用于拷贝java字符串,无对应Release方法,但如果buffer不在栈上,需要手动释放buffer。
(4)GetStringCritical可提高直接获取源java字符串的概率,但会暂停GC,在Get/ReleaseStringCritical之间不要调用任何会阻塞当前线程(如 IO 之类的)或分配对象的函数,也最好尽量避免调用其他 JNI 方法,除带有Critical后缀的方法。

 好了分享就到这里,如有问题请告知,希望大家点个赞支持一下!!!

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值