我围绕本机 C 库创建了一个 Java 包装器,并且对字符串编码有疑问。Java 使用的“Java 修改的 UTF-8”编码与常规 UTF-8 略有不同。这些差异可能会导致严重的问题:JNI 函数在通过常规 UTF-8 时可能会导致应用程序崩溃,因为它可能包含“Java 修改的 UTF-8”禁止的字节序列。请参阅以下主题:“Java Modified UTF-8 Encoding”是什么意思?
我的问题是将“Java 修改的 UTF-8”转换为常规 UTF-8 并返回的标准可靠方法是什么?
我围绕本机 C 库创建了一个 Java 包装器,并且对字符串编码有疑问。Java 使用的“Java 修改的 UTF-8”编码与常规 UTF-8 略有不同。这些差异可能会导致严重的问题:JNI 函数在通过常规 UTF-8 时可能会导致应用程序崩溃,因为它可能包含“Java 修改的 UTF-8”禁止的字节序列。请参阅以下主题:“Java Modified UTF-8 Encoding”是什么意思?
我的问题是将“Java 修改的 UTF-8”转换为常规 UTF-8 并返回的标准可靠方法是什么?
感谢大家的回复!我终于找到了答案。这种转换的唯一记录方式是使用 InputStreamReader 和 OutputStreamWriter
在正常使用中,Java 编程语言通过 InputStreamReader 和 OutputStreamWriter 读写字符串时支持标准的 UTF-8(如果是平台的默认字符集或程序要求的)。
https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8
此外,NewStringUTF JNI 方法需要修改后的 UTF-8 输入,而不是标准输入。如果它接收到一个禁止的字节序列并且 JNI 异常处理不能阻止它使应用程序崩溃,它将使应用程序崩溃。
所以我的第二个结论是,将 String/jstring 从 JNI 传递到 Java 或其他方式总是一个坏主意。永远不要那样做。使用 Java 层上的 InputStreamReader 和 OutputStreamWriter 执行所有转换,并将原始字节数组传递到 JNI 或从 JNI 传递原始字节数组。
绝对没有什么只能通过使用某些库来实现。你总是可以自己做。
注意:Buffer
下面的类只是包装一个数组,byte
就像 aString
包装一个char
.
public static String stringFromBuffer( Buffer buffer )
{
String result = stringFromBuffer0( buffer );
assert bufferFromString0( result ).equals( buffer );
return result;
}
public static Buffer bufferFromString( String s )
{
Buffer result = bufferFromString0( s );
assert stringFromBuffer( result ).equals( s );
return result;
}
private static String stringFromBuffer0( Buffer buffer )
{
byte[] bytes = buffer.getBytes();
int end = bytes.length;
char[] chars = new char[end];
int t = 0;
for( int s = 0; s < end; )
{
int b1 = bytes[s++] & 0xff;
assert b1 >> 4 >= 0;
if( /*b1 >> 4 >= 0 &&*/ b1 >> 4 <= 7 ) /* 0x0xxx_xxxx */
chars[t++] = (char)b1;
else if( b1 >> 4 >= 8 && b1 >> 4 <= 11 ) /* 0x10xx_xxxx */
throw new MalformedUtf8Exception( s - 1 );
else if( b1 >> 4 >= 12 && b1 >> 4 <= 13 ) /* 0x110x_xxxx 0x10xx_xxxx */
{
assert s < end : new IncompleteUtf8Exception( s - 1 );
int b2 = bytes[s++] & 0xff;
assert (b2 & 0xc0) == 0x80 : new MalformedUtf8Exception( s - 1 );
chars[t++] = (char)(((b1 & 0x1f) << 6) | (b2 & 0x3f));
}
else if( b1 >> 4 == 14 ) /* 0x1110_xxxx 0x10xx_xxxx 0x10xx_xxxx */
{
assert s < end : new IncompleteUtf8Exception( s - 1 );
int b2 = bytes[s++] & 0xff;
assert (b2 & 0xc0) == 0x80 : new MalformedUtf8Exception( s - 1 );
assert s < end : new IncompleteUtf8Exception( s - 1 );
int b3 = bytes[s++] & 0xff;
assert (b3 & 0xc0) == 0x80 : new MalformedUtf8Exception( s - 1 );
chars[t++] = (char)(((b1 & 0x0f) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f));
}
else /* 0x1111_xxxx */
throw new MalformedUtf8Exception( s - 1 );
}
return new String( chars, 0, t );
}
private static Buffer bufferFromString0( String s )
{
char[] chars = s.toCharArray();
byte[] bytes = new byte[chars.length * 3];
int p = 0;
for( char c : chars )
{
if( (c >= 1) && (c <= 0x7f) )
bytes[p++] = (byte)c;
else if( c > 0x07ff )
{
bytes[p++] = (byte)(0xe0 | ((c >> 12) & 0x0f));
bytes[p++] = (byte)(0x80 | ((c >> 6) & 0x3f));
bytes[p++] = (byte)(0x80 | (c & 0x3f));
}
else
{
bytes[p++] = (byte)(0xc0 | ((c >> 6) & 0x1f));
bytes[p++] = (byte)(0x80 | (c & 0x3f));
}
}
if( p > 0xffff )
throw new StringTooLongException( p );
return Buffer.create( bytes, 0, p );
}
我的问题是将“Java 修改的 UTF-8”转换为常规 UTF-8 并返回的标准可靠方法是什么?
首先,考虑您是否真的需要或想要这样做。在包装 C 库的上下文中,我能想到这样做的唯一原因是使用与 Java 一起工作的 JNI 函数,String
以修改后的 UTF-8 编码的字节数组,但这既不是唯一也不是最好的方法除非在相当特殊的情况下继续进行。
在大多数情况下,我建议直接从 UTF-8 转到 String 对象,并让 Java 完成大部分工作。Java 提供的简单工具包括构造函数String(byte[], String)
,它使用您指定其编码的数据初始化一个字符串,以及String.getBytes(String)
,它以您选择的编码为您提供字符串的字符数据。这两者都仅限于 JVM 已知的编码,但 UTF-8 保证在其中。您可以直接从您的 JNI 代码中使用它们,或者为您的 JNI 代码调用提供合适的专用包装器方法。
如果您确实想要修改后的 UTF-8 形式,那么您的 JNI 代码可以通过GetStringUTFChars
JNI 函数从相应的 Java 字符串(如上总结)中获取它,您可以使用NewStringUTF
. 当然,这使得 JavaString
成为中间形式,在这种情况下是完全合适的。