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

编程入门 行业动态 更新时间:2024-10-08 20:33:11

Android NDK开发(八):JNI函数接口详解—<a href=https://www.elefans.com/category/jswz/34/1771434.html style=字符串处理"/>

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后缀的方法。

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

更多推荐

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

本文发布于:2024-02-07 06:10:32,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1753755.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:字符串   详解   函数   接口   Android

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!