Java 使用的是 UTF-8 还是 UTF-16 编码?
Which encoding does Java uses UTF-8 or UTF-16?
我已经阅读了以下帖子:
- What is the Java's internal represention for String? Modified UTF-8? UTF-16?
- https://docs.oracle.com/javase/8/docs/api/java/lang/String.html
现在考虑下面给出的代码:
public static void main(String[] args) {
printCharacterDetails("最");
}
public static void printCharacterDetails(String character){
System.out.println("Unicode Value for "+character+"="+Integer.toHexString(character.codePointAt(0)));
byte[] bytes = character.getBytes();
System.out.println("The UTF-8 Character="+character+" | Default: Number of Bytes="+bytes.length);
String stringUTF16 = new String(bytes, StandardCharsets.UTF_16);
System.out.println("The corresponding UTF-16 Character="+stringUTF16+" | UTF-16: Number of Bytes="+stringUTF16.getBytes().length);
System.out.println("----------------------------------------------------------------------------------------");
}
当我尝试调试上面代码中的 character.getBytes()
行时,调试器将我带到了 String class 的 getBytes()
方法,然后进入了 static byte[] encode(char[] ca, int off, int len)
StringCoding 方法 class。 encode 方法的第一行 (String csn = Charset.defaultCharset().name();
) 在调试期间返回 "UTF-8" 作为默认编码。我预计它会是 "UTF-16"。
程序的输出是:
Unicode 值最=6700
UTF-8 字符=最 |默认值:字节数=3
对应的UTF-16字符=� | UTF-16:字节数=6
当我在程序中将它显式转换为 UTF-16 时,它需要 6 个字节来表示字符。它不应该为 UTF-16 使用 2 或 4 个字节吗?为什么使用了 6 个字节?
我的理解哪里出错了?
我使用 Ubuntu 14.04,locale 命令显示如下:
LANG=en_US.UTF-8
这是否意味着 JVM 根据底层 OS 决定使用哪种编码,还是仅使用 UTF-16?
请帮助我理解这个概念。
字符是图形实体,是人类文化的一部分。当计算机需要处理文本时,它会使用这些字符的表示(以字节为单位)。使用的确切表示称为 encoding.
有许多编码可以表示相同的字符 - 通过 Unicode 字符集,或通过其他字符集,如各种 ISO-8859 编码或 JIS X 0208。
在内部,Java 使用 UTF-16。这意味着每个字符都可以由一个或两个两个字节的序列表示。您使用的字符最具有代码点 U+6700,在 UTF-16 中表示为字节 0x67 和字节 0x00。
那是内部编码。你看不到它,除非你转储内存并查看转储图像中的字节。
但是方法 getBytes()
没有 return 这种内部表示。它的文档说:
public byte[] getBytes()
Encodes this String
into a sequence of bytes
using the platform's default charset, storing the result into a new
byte array.
"platform's default charset" 是您的语言环境变量所说的。即UTF-8
。所以它采用 UTF-16 内部表示,并将其转换为不同的表示 - UTF-8。
注意
new String(bytes, StandardCharsets.UTF_16);
不是"convert it to UTF-16 explicitly",正如您假设的那样。这个字符串构造函数接受一个字节序列,它应该是你在第二个参数中给出的编码,并将其转换为这些字节在该编码中表示的任何字符的 UTF-16 表示。
但是你已经给了它一个以 UTF-8 编码的字节序列,并告诉它把它解释为 UTF-16。这是错误的,您没有得到您期望的字符或字节。
您无法告诉 Java 如何在内部存储字符串。它始终将它们存储为 UTF-16。构造函数 String(byte[],Charset)
告诉 Java 从应该在给定字符集中的字节数组创建 UTF-16 字符串。方法 getBytes(Charset)
告诉 Java 给你一个字节序列,代表给定编码(字符集)中的字符串。不带参数的方法 getBytes()
的作用相同 - 但使用平台的默认字符集进行转换。
所以你误解了 getBytes()
给你的东西。它不是内部表示。你不能直接得到它。只有 getBytes(StandardCharsets.UTF_16)
会给你那个,而且只是因为你知道 UTF-16
是 Java 的内部表示。如果 Java 的未来版本决定以不同的编码表示字符,那么 getBytes(StandardCharsets.UTF_16)
将不会向您显示内部表示。
编辑: 事实上,Java 9 引入了字符串内部表示的这种变化,默认情况下,所有字符都属于 ISO 的字符串-8859-1 范围在内部以 ISO-8859-1 表示,而至少有一个字符超出该范围的字符串在内部以 UTF-16 表示,如前所述。所以确实,getBytes(StandardCharsets.UTF_16)
不再是 return 的内部表示。
如上所述,java 使用 UTF-16 作为字符数据的编码。
可以补充的是,可表示的字符集仅限于整个 Unicode 字符集的适当子集。 (我相信 java 将其字符集限制为 Unicode BMP,所有这些都适合两个字节的 UTF-16。)
所以应用的编码确实是 UTF-16,但是应用它的字符集是整个 Unicode 字符集的一个适当的子集,这保证了 Java 每个标记总是使用两个字节在其内部字符串编码中。
我已经阅读了以下帖子:
- What is the Java's internal represention for String? Modified UTF-8? UTF-16?
- https://docs.oracle.com/javase/8/docs/api/java/lang/String.html
现在考虑下面给出的代码:
public static void main(String[] args) {
printCharacterDetails("最");
}
public static void printCharacterDetails(String character){
System.out.println("Unicode Value for "+character+"="+Integer.toHexString(character.codePointAt(0)));
byte[] bytes = character.getBytes();
System.out.println("The UTF-8 Character="+character+" | Default: Number of Bytes="+bytes.length);
String stringUTF16 = new String(bytes, StandardCharsets.UTF_16);
System.out.println("The corresponding UTF-16 Character="+stringUTF16+" | UTF-16: Number of Bytes="+stringUTF16.getBytes().length);
System.out.println("----------------------------------------------------------------------------------------");
}
当我尝试调试上面代码中的 character.getBytes()
行时,调试器将我带到了 String class 的 getBytes()
方法,然后进入了 static byte[] encode(char[] ca, int off, int len)
StringCoding 方法 class。 encode 方法的第一行 (String csn = Charset.defaultCharset().name();
) 在调试期间返回 "UTF-8" 作为默认编码。我预计它会是 "UTF-16"。
程序的输出是:
Unicode 值最=6700 UTF-8 字符=最 |默认值:字节数=3
对应的UTF-16字符=� | UTF-16:字节数=6
当我在程序中将它显式转换为 UTF-16 时,它需要 6 个字节来表示字符。它不应该为 UTF-16 使用 2 或 4 个字节吗?为什么使用了 6 个字节?
我的理解哪里出错了? 我使用 Ubuntu 14.04,locale 命令显示如下:
LANG=en_US.UTF-8
这是否意味着 JVM 根据底层 OS 决定使用哪种编码,还是仅使用 UTF-16? 请帮助我理解这个概念。
字符是图形实体,是人类文化的一部分。当计算机需要处理文本时,它会使用这些字符的表示(以字节为单位)。使用的确切表示称为 encoding.
有许多编码可以表示相同的字符 - 通过 Unicode 字符集,或通过其他字符集,如各种 ISO-8859 编码或 JIS X 0208。
在内部,Java 使用 UTF-16。这意味着每个字符都可以由一个或两个两个字节的序列表示。您使用的字符最具有代码点 U+6700,在 UTF-16 中表示为字节 0x67 和字节 0x00。
那是内部编码。你看不到它,除非你转储内存并查看转储图像中的字节。
但是方法 getBytes()
没有 return 这种内部表示。它的文档说:
public byte[] getBytes()
Encodes this
String
into a sequence of bytes using the platform's default charset, storing the result into a new byte array.
"platform's default charset" 是您的语言环境变量所说的。即UTF-8
。所以它采用 UTF-16 内部表示,并将其转换为不同的表示 - UTF-8。
注意
new String(bytes, StandardCharsets.UTF_16);
不是"convert it to UTF-16 explicitly",正如您假设的那样。这个字符串构造函数接受一个字节序列,它应该是你在第二个参数中给出的编码,并将其转换为这些字节在该编码中表示的任何字符的 UTF-16 表示。
但是你已经给了它一个以 UTF-8 编码的字节序列,并告诉它把它解释为 UTF-16。这是错误的,您没有得到您期望的字符或字节。
您无法告诉 Java 如何在内部存储字符串。它始终将它们存储为 UTF-16。构造函数 String(byte[],Charset)
告诉 Java 从应该在给定字符集中的字节数组创建 UTF-16 字符串。方法 getBytes(Charset)
告诉 Java 给你一个字节序列,代表给定编码(字符集)中的字符串。不带参数的方法 getBytes()
的作用相同 - 但使用平台的默认字符集进行转换。
所以你误解了 getBytes()
给你的东西。它不是内部表示。你不能直接得到它。只有 getBytes(StandardCharsets.UTF_16)
会给你那个,而且只是因为你知道 UTF-16
是 Java 的内部表示。如果 Java 的未来版本决定以不同的编码表示字符,那么 getBytes(StandardCharsets.UTF_16)
将不会向您显示内部表示。
编辑: 事实上,Java 9 引入了字符串内部表示的这种变化,默认情况下,所有字符都属于 ISO 的字符串-8859-1 范围在内部以 ISO-8859-1 表示,而至少有一个字符超出该范围的字符串在内部以 UTF-16 表示,如前所述。所以确实,getBytes(StandardCharsets.UTF_16)
不再是 return 的内部表示。
如上所述,java 使用 UTF-16 作为字符数据的编码。
可以补充的是,可表示的字符集仅限于整个 Unicode 字符集的适当子集。 (我相信 java 将其字符集限制为 Unicode BMP,所有这些都适合两个字节的 UTF-16。)
所以应用的编码确实是 UTF-16,但是应用它的字符集是整个 Unicode 字符集的一个适当的子集,这保证了 Java 每个标记总是使用两个字节在其内部字符串编码中。