JNI 编程上手指南之字符串处理

引子

JNI 把 Java 中的对象当作一个 C 指针传递到本地方法中,这个指针指向 JVM 中的内部数据结构,通常我们是通过 JNIEnv 中的函数来操作这些数据结构从而我们无需关心这个数据结构的具体构造。

字符串处理示例

我们来看之前分享过的一个例子:

Java 层:

private native String sayHello(String msg);

C/C++ 层:

jJNIEXPORT jstring JNICALL Java_HelloJNI_sayHello__Ljava_lang_String_2(JNIEnv *env, jobject jobj, jstring str) {

    //jstring -> char*
    jboolean isCopy;
    //GetStringChars 用于 utf-16 编码
    //GetStringUTFChars 用于 utf-8 编码
    const char* cStr = env->GetStringUTFChars(str, &isCopy);

    //异常处理,后面会专门讲,这里了解即可
    if (nullptr == cStr) {
        return nullptr;
    }

    //这个取决于 jvm 的实现,不影响我们的编程
    if (JNI_TRUE == isCopy) {
        cout << "C 字符串是 java 字符串的一份拷贝" << endl;
    } else {
        cout << "C 字符串指向 java 层的字符串" << endl;
    }

    cout << "C/C++ 层接收到的字符串是 " << cStr << endl;

    //通过JNI GetStringChars 函数和 GetStringUTFChars 函数获得的C字符串在原生代码中
    //使用完之后需要正确地释放,否则将会引起内存泄露。
    env->ReleaseStringUTFChars(str, cStr);

    string outString = "Hello, JNI";
    // char* 转换为 jstring
    return env->NewStringUTF(outString.c_str());
}

我们访问 java.lang.String 对应的 JNI 类型 jstring 时,没有像访问基本数据类型一样直接使用,因为它在 Java 是一个引用类型。那么我们在 JNI 中,怎么操作处理 jstring 数据呢,我们可以通过 JNIEnv 结构体提供的函数的 JNI 函数来访问字符串的内容。

JNI 字符串处理函数

Java 中参数的类型是 String,JNI 函数中对应的类型是 jstring,jstring 是一个引用类型,我们需要使用 JNIEnv 中的函数来操作 jstring,接下来我们介绍一些操作 jstring 的常用函数:

GetStringUTFChars

// 参数说明:
// * this: JNIEnv 指针
// * string: jstring类型(Java 传递给本地代码的字符串指针)
// * isCopy: 它的取值可以是 JNI_TRUE (值为1)或者为 JNI_FALSE (值为0)。如果值为 JNI_TRUE,表示返回 JVM 内部源字符串的一份拷贝,并为新产生的字符串分配内存空间。如果值为 JNI_FALSE,表示返回 JVM 内部源字符串的指针,意味着可以通过指针修改源字符串的内容,不推荐这么做,因为这样做就打破了 Java 字符串不能修改的规定。但我们在开发当中,并不关心这个值是多少,通常情况下这个参数填 NULL 即可。
const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*);//C环境中的定义

const char* GetStringUTFChars(jstring string, jboolean* isCopy)//C++环境中的定义
{ return functions->GetStringUTFChars(this, string, isCopy); }

Java 默认使用 UTF-16 编码,而 C/C++ 默认使用 UTF-8 编码。GetStringUTFChars 可以把一个 jstring 指针(指向 JVM 内部的 UTF-16 字符序列)转换成一个 UTF-8 编码的 C 风格字符串。

调用完 GetStringUTFChars 之后不要忘记安全检查,因为 JVM 可能需要为新诞生的字符串分配内存空间,当内存空间不够分配的时候,会导致调用失败,失败后 GetStringUTFChars 会返回 NULL,并抛出一个 OutOfMemoryError 异常。JNI的异常和 Java 中的异常处理流程是不一样的,Java 遇到异常如果没有捕获,程序会立即停止运行。而 JNI 遇到未决的异常不会改变程序的运行流程,也就是程序会继续往下走,这样后面针对这个字符串的所有操作都是非常危险的,因此,我们需要用 return 语句跳过后面的代码,并立即结束当前方法。关于 JNI 中异常的处理我们会在后续文章中解析。

ReleaseStringUTFChars

// 参数说明:
// this: JNIEnv 指针
// string: 指向一个 jstring 变量,即是要释放的本地字符串的来源。在当前环境下指向 Java 中传递过来的 String 字符串对应的 JNI 数据类型 jstring
// utf:将要释放的C/C++本地字符串。即我们调用GetStringUTFChars获取的数据的存储指针。
void (*ReleaseStringUTFChars)(JNIEnv*, jstring, const char*);//C中的定义

void ReleaseStringUTFChars(jstring string, const char* utf)//C++中的定义
{ functions->ReleaseStringUTFChars(this, string, utf); }

ReleaseStringUTFChars 函数用于通知虚拟机 jstring 在 jvm 中对应的内存已经不使用了,可以清除了。

NewStringUTF

// 参数说明
// this: JNIEnv 指针
// bytes: 指向一个char * 变量,即要返回给 Java 层的 C/C++ 中字符串。
jstring  (*NewStringUTF)(JNIEnv*, const char*);//C环境中定义

jstring NewStringUTF(const char* bytes)//C++环境中的定义
{ return functions->NewStringUTF(this, bytes); }

NewStringUTF 构建一个新的 java.lang.String 字符串对象。这个新创建的字符串会自动转换成 Java 支持的 UTF-16 编码。

与 GetStringUTFChars 相同,NewStringUTF 在内存不足时抛出 OutOfMemoryError 异常。

NewString

jstring (NewString)(JNIEnv env, const jchar* unicodeChars, jsize size);

利用 UTF-16 字符数组构造新的 java.lang.String 对象。

与 GetStringUTFChars 相同,NewString 在内存不足时抛出 OutOfMemoryError 异常。

GetStringUTFLength

jsize (GetStringUTFLength)(JNIEnv env, jstring string);

返回字符串的 UTF-8 编码的长度,即 C 风格字符串的长度。

GetStringLength

const jchar* (GetStringChars)(JNIEnv env, jstring string, jboolean* isCopy);

返回字符串的 UTF-16 编码的长度,即 Java 字符串长度

GetStringChars

const jchar* (GetStringChars)(JNIEnv env, jstring string, jboolean* isCopy);

返回字符串 string 对应的 UTF-16 字符数组的指针。

与 GetStringUTFChars 相同,GetStringChars 在内存不足时抛出 OutOfMemoryError 异常。

ReleaseStringChars

void ReleaseStringChars (JNIEnv *env, jstring string, const jchar *chars);

通知虚拟机平台释放 chars 所引用的相关资源,以免造成内存泄漏。参数chars 是一个指针,可通过 GetStringChars() 从 string 获得

GetStringCritical 和 ReleaseStringCritical

该对函数主要是为了提高从虚拟机平台返回源字符串直接指针的可能性。Get/ReleaseStringChars 和 Get/ReleaseStringUTFChars 这对函数返回的源字符串会后分配内存,如果有一个字符串内容相当大,有 1M 左右,而且只需要读取里面的内容打印出来,用这两对函数就有些不太合适了。此时用 Get/ReleaseStringCritical 可直接返回源字符串的指针应该是一个比较合适的方式。不过这对函数有一个很大的限制,在这两个函数之间的本地代码不能调用任何会让线程阻塞或等待 JVM 中其它线程的本地函数或 JNI 函数。因为通过 GetStringCritical 得到的是一个指向 JVM 内部字符串的直接指针,获取这个直接指针后会导致暂停 GC 线程,当 GC 被暂停后,如果其它线程触发 GC 继续运行的话,都会导致阻塞调用者。所以在Get/ReleaseStringCritical 这对函数中间的任何本地代码都不可以执行导致阻塞的调用或为新对象在 JVM 中分配内存,否则,JVM 有可能死锁。另外一定要记住检查是否因为内存溢出而导致它的返回值为 NULL,因为 JVM 在执行 GetStringCritical 这个函数时,仍有发生数据复制的可能性,尤其是当 JVM 内部存储的数组不连续时,为了返回一个指向连续内存空间的指针,JVM 必须复制所有数据。

与 GetStringUTFChars 相同,GetStringCritical 也可能在内存不足时抛出 OutOfMemoryError 异常。

GetStringRegion 和 GetStringUTFRegion

分别表示获取 UTF-16 和 UTF-8 编码字符串指定范围内的内容。这对函数会把源字符串复制到一个预先分配的缓冲区内。

NIEXPORT jstring JNICALL Java_HelloJNI_sayHello__Ljava_lang_String_2(JNIEnv *env, jobject jobj, jstring str) {
    char buff[128];
    jsize len = env->GetStringUTFLength(str); // 获取 utf-8 字符串的长度
    // 将虚拟机平台中的字符串以 utf-8 编码拷入C缓冲区,该函数内部不会分配内存空间
    env->GetStringUTFRegion(str,0,len,buff);
}

总结

  • 对于小字符串来说,GetStringRegion 和 GetStringUTFRegion 这两对函数是最佳选择,因为缓冲区可以被编译器提前分配,而且永远不会产生内存溢出的异常。当你需要处理一个字符串的一部分时,使用这对函数也是不错。因为它们提供了一个开始索引和子字符串的长度值。另外,复制少量字符串的消耗也是非常小的。
  • 使用 GetStringCritical 和 ReleaseStringCritical 这对函数时,必须非常小心。一定要确保在持有一个由 GetStringCritical 获取到的指针时,本地代码不会在 JVM 内部分配新对象,或者做任何其它可能导致系统死锁的阻塞性调用。
  • 获取 Unicode 字符串和长度,使用 GetStringChars 和 GetStringLength 函数。获取 UTF-8 字符串的长度,使用 GetStringUTFLength 函数。
  • 创建 Unicode 字符串,使用NewString,创建UTF-8使用 NewStringUTF 函数。
  • 通过 GetStringUTFChars、GetStringChars、GetStringCritical 获取字符串,这些函数内部会分配内存,必须调用相对应的 ReleaseXXXX 函数释放内存。
阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/archives/20400,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?