在Java中,创建一个 String
对象的方式非常简单,而且可以使用 String
类的各种方法很方便的操作字符串。然而在JNI中,虽然 jstring
类表示Java的 String
类,但是 jstring
并没有提供任何函数来操作字符串。
对于一个字符串常量,例如 "abc"
,在Java中是用一个 String
对象表示,并且代表 UTF-16
编码格式的字符串(也就是双字节编码)。而在JNI中,是用一个 char
类型指针来表示,编码格式却为 modified UTF-8
。
那么什么是 modified UTF-8
编码格式呢?其实它与 UTF-8
格式非常像,但是有点小小的差别
UTF-8
中是用一个字节表示,然而在 modified UTF-8
中,是用两个字节表示。 modified。 UTF-8
使用的是两个三字节来实现的。
如果想了解 UTF-8
和 modified UTF-8
编码格式,可以参考文末链接。
JNI中对字符串的操作的函数,都有针对这两个格式的版本。下面我对两种编码格式的字符串操作的函数做一个概括性的讲解。
jstring NewString(JNIEnv *env, const jchar *unicodeChars, jsize len); 复制代码
参数
jchar *unicodeChars
: 指向 Unicode
编码字符串的指针。 jsize len
: 字符串的长度。
NewString
使用 Unicode
编码的字符数组创建一个Java的 String
对象。
使用 NewString
的关键就是必须使用 Unicode
编码的字符串,而这里的 Unicode
编码一般都是指 UTF-16
编码。但是如何获取这个 UTF-16
编码的字符串呢,这个超出本文讨论的范畴,但是在Android开发中,有一个 String16
的类,通过构造函数就可以把一个字符串常量转化为 UTF-16
编码的字符串。
jsize GetStringLength(JNIEnv *env, jstring string); 复制代码
参数
jstring string
: Java的 String
对象。
GetStringLength
函数返回的字符串长度与Java的 String
返回的字符串的长度的是一样的,例如 String s = "中国";
,这个Java字符串长度为2,又例如 String s = "China";
,这个字符串长度为5。
const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy); 复制代码
参数
jstring string
: Java的 String
对象 jboolean * isCopy
: 如果 isCopy
不为 NULL
,并且函数生成的是字符串的拷贝,那么 *isCopy
的值为 JNI_TRUE
,如果没有生成拷贝,那么 *isCopy
值为 JNI_FALSE
GetStringChars
返回一个指向 Unicode
编码的字符数组的指针(指针可能为 NULL
)。这个指针可能指向原字符串的数组,也可能指向拷贝的字符数组,取决与虚拟机的实现。如果是生成拷贝,就需要释放本地字符串,需要使用 ReleaseStringChars
。
void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars); 复制代码
参数
jstring string
: Java的 String
对象 jchar *chars
: 指向由 GetStringChars
返回的 Unicode
编码的字符数组
ReleaseStringChars
函数并不是自己去释放本地字符串(如果发生拷贝),而是通知虚拟机本地代码不再访问 jchar *chars
,然后虚拟机自己决定如何处理。
void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf); 复制代码
参数
jstring str
: Java的 String
对象 jsize start
: 拷贝的开始位置 jsize len
: 拷贝的长度 jchar *buf
: 拷贝的目标缓冲区
GetStringRegion
函数从 start
开始,拷贝 len
长度的 Unicode
字符到 buf
中。
const jchar * GetStringCritical(JNIEnv *env, jstring string, jboolean *isCopy); void ReleaseStringCritical(JNIEnv *env, jstring string, const jchar *carray); 复制代码
Get/ReleaseStringCritical
函数与 Get/ReleaseStringChars
函数在使用和功能上是一样的。也就是说, GetStringCritical
函数可能返回一个指向原字符串的指针,或者返回一个指向源字符串的拷贝的指针,这取决与虚拟机实现。如果发生了拷贝,那么就需要 ReleaseStringCritial
来通知虚拟机本地进行释放操作。
jstring NewStringUTF(JNIEnv *env, const char *bytes); 复制代码
NewStringUTF
的第二个参数 const char *bytes
是 modified UTF-8
编码的字节数组。
modified UTF-8
是JNI特有的格式,使用这种格式的字符串与虚拟机中使用的字符串一样。
JNI使用 modified UTF-8
编码来表示各种字符串类型,例如下面两行代码都是使用这种编码的字符串
const char * bytes1 = "中国"; const char * bytes2 = "China"; 复制代码
由于创建的字符串常量默认就是 modified UTF-8
编码的,因此 NewStringUTF
也就是大家最常用的来获取Java的 String
对象的函数。
jsize GetStringUTFLength(JNIEnv *env, jstring string); 复制代码
参数
jstring string
: Java的 String
对象
GetStringUTFLength
函数返回一个 modified UTF-8
编码格式字符串的字节长度。
注意, GetStringUTFLength
返回的是字节长度,而 GetStringLength
返回的字符长度。一个是强调字节,一个是强调字符,这是有却别的。例如对于一个Java的字符串 String s = "中国"
, GetStringUTFLength
返回的值为6,代表6个字节长度,而 GetStringLength
返回的值为2,代表2个字符长度。
const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy); 复制代码
参数
jstring string
: Java的String对象 jboolean * isCopy
: 如果 isCopy
不为 NULL
,并且如果函数对原字符串进行了拷贝,那么 *isCopy
的值为 JNI_TRUE
,如果没有发生拷贝,那么 *isCopy
的值为 JNI_FALSE
。
GetStringUTFChars
函数返回一个指向 modified UTF-8
编码的字节数组指针。
从参数 isCopy
可以看出, GetStringUTFChars
函数返回的指向字节数组的指针,可能指向远字符串,也可能指向拷贝的字符串。如果一旦发生了拷贝,那么在不需要这个拷贝的时候,就需要进行释放,可以调用 ReleaseStringUTFChars
函数。
void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf); 复制代码
参数
jstring string
: Java的 String
对象 const char *utf
: 由 GetStringUTFChars
获取。
ReleaseStringUTFChars
函数只是通知虚拟机,本地代码不再访问 const char *utf
,之后的处理动作取决与虚拟机。
void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize len, char *buf); 复制代码
GetStringUTFRegion
函数与 GetStringRegion
函数的参数是一样,不同的是 GetStringUTFRegion
函数会把拷贝到缓冲区的字符从 Unicode
转化为 modified UTF-8
格式。
一般情况下,我们都选择使用JNI默认的 modified UTF-8
编码字符串,因为很方便,不需要转换编码。而如果一定要使用 UTF-16
编码字符串,那么就需要进行转换。
那么如何在JNI层更好的处理字符串呢?如果使用的是C++开发JNI,那么可以在JNI层使用 string
类来操作字符串,而如果使用C语言,那么不得不使用字符指针一步一步来处理。
例如,一个Java类中有一个 native
方法
public class Hello { native string getHelloFromJNI(String name); } 复制代码
在JNI层对应的实现为
static jstring getHello(JNIEnv *env, jobject thiz, jstring name) { // 从Java的String对象转换为本地表示 const char *c_name = env->GetStringUTFChars(name, NULL); // 使用C++的string进行字符串拼接 string str("Hello, "); str.append(c_name); str.append("!"); env->ReleaseStringUTFChars(name, c_name); // 创建Java的String对象并返回 return env->NewStringUTF(str.c_str()); } 复制代码