Java 字符串编码

要搞清楚Java字符(串)编码,先得区分清楚内码(internal encoding)和外码(external encoding)

  • 内码是程序内部使用的字符编码,特别是某种语言实现其char或String类型在内存里用的内部编码
  • 外码是程序与外部交互时外部使用的字符编码

Java使用UTF-16作为字符的内码

然而UTF-16在Java设计之初还是定长编码,后来Unicode涵盖的字符变多了之后,UTF-16变成了两字节或者四字节的变长编码

Java也只好跟进,为了实现UTF-16的变长编码语义,Java规定:

  • 一个完整的字符是一个code point
  • 一个code point可以对应 1 到 2 个code unit
  • 一个code unit是固定的两字节

备注:本文中提到的关于:UTF-16编码,Unicode,BOM等信息,不是很清楚的同学可以查看我的另一篇文章 字符集和字符编码

char

对应上述的一个code unit,固定为两个字节,也可以理解成是一个两字节的无符号整型

只有只需1个code unit的code point才可以完整的存在char里,所以char类型不一定能表示一个UTF-16的字符

unicode

char是固定的两个字节,所以在java中使用unicode编码只能这样:

  • 一个char ‘\uXXXX’
  • 两个char(String) “\uXXXX\uXXXX”
1
2
char u = '\u4E2D'; // 中
String u2 = "\uD801\uDC0F"; // ? 一个特殊字符, length 2

String

String作为char的序列,对于两字节的UTF-16只需要用一个char表示,对于需要2个code unit(四字节)的UTF-16可以包含由两个char组成的 “surrogate pair” 来表示

为此Java的标准库新加了一套用于访问code point的API,而这套API就表现出了UTF-16的变长特性, 包括String, StringBuffer, StringBuilder等

getBytes

上面说了这么多内码,下面说说我们程序与外部交互时更加常用用的外码

String.getBytes()是一个用于将String的内码转换为指定的外码的方法:

  • 无参数版使用平台的默认编码作为外码
  • 有参数版使用参数指定的编码作为外码

将String的内容用外码编码后,结果放在一个新byte[]返回

CompressedString

由于Java使用UTF-16作为内码,只有2和4字节两种情况降低了复杂的,然而很多时候我们用到的字符使用一个字节的ASCII就可以表示,使用两个字节无疑增加了开销

所以在Sun JDK6中有一个压缩字符串(-XX:+UseCompressedString)的功能。启用后,String内部存储字符串内容可能用char[],也可能用byte[];

当整个字符串所有字符都在ASCII编码范围内时,就使用byte[](ASCII序列)来存储,此时字符串就处于压缩状态

然而压缩字符串这个功能的实现并不够理想,实现的太复杂而且效果未如预期的好,所以后续JDK版本中并没有包含,后续版本可能改进后继续加入该功能

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
String str = "z中";
// 2
System.out.println(str.length());

String str1 = "z中\uD852\uDF62";
// **输出三个字符**, 后面两个char组成一个Unicode字符
System.out.println(str1);
// 4
System.out.println(str1.length());


// 1: 默认UTF-8, ASCII 使用一个字节
System.out.println("z".getBytes().length);
// 3: 默认UTF-8, 中文使用三个字节
System.out.println("中".getBytes().length);

// 下面两个都是返回4
// 虽然两个字符都是在UTF-16的两个字节表示范围内, 但是UTF-16默认会加上两个BOM信息
System.out.println("z".getBytes("UTF-16").length);
System.out.println("中".getBytes("UTF-16").length);
// FEFF4E2D (FEFF为大端序BOM)
System.out.println(getHexString("中".getBytes("UTF-16")));

// 下面两个都是返回2, 指定BOM顺序后,不输出BOM信息,
System.out.println("z".getBytes("UTF-16BE").length);
System.out.println("中".getBytes("UTF-16BE").length);
// 4E2D
System.out.println(getHexString("中".getBytes("UTF-16BE")));