Unicode、UTF-8、UTF-16、UTF-32、ASCII、GBK、GB2312、ISO-8859-1 它们之间是什么关系?

关于这几种字符编码的关系,经过各种资料研究,总结如下图(请右键在新标签页打开查看或者下载后使用看图工具放大查看):

Java 中 char 和 Unicode、UTF-8、UTF-16、ASCII、GBK 的关系-LMLPHP

我们应该从历史的顺序看待这些字符编码的由来:

  • ASCII(早期):早期提出的一种字符编码集,总共有 128 个字符,可以用 1 个字节 8 个比特位的低 7 位表示,也可以用十进制数字:[0-127]每个数字表示一个字符。全部的ASCII码参考这里
  • ISO-8859-1:256 个字符编码,西欧语言对 ASCII 码的扩展。
  • GB2312:汉字编码集,对 ASCII 的扩展。采用双字节表示编码范围:A1-F7,其中从A1-A9是符号区,总共包含 682 个符号,6763 个汉字。
  • GBK:主要针对 GB2312 的扩展,采用双字节表示编码范围:[8140,FEFE](剔除XX7F) 共 23940 个码位,共收录了 21003 个汉字。一个中文字符占2个字节GBKUnicode 是完全不同的两种编码形式,没有转换规则,互相之间的转换只能通过查表。
  • Unicode(现代):对 ASCII 的扩展,一种规模巨大的字符编码集,可容纳100多万种符号,包括了地球上所有文化、所有字母和符号的编码。每一种编码对应了一个字符, Unicode 采用 4~6HEX 码表示一个 Unicode 码点,范围是[U+0000~U+10FFFF],目前仍在更新中。

由于 Unicode 只规定了表示符号的二进制代码,却没有规定如何存储这个二进制代码。所以如何存储 Unicode 有不同的实现。而 UTF-8UTF-16UTF-32 就是针对 Unicode 的不同存储方式的具体实现。

  • UTF-8可变长编码,可以使用 1~4 个字节表示一个符号,根据不同符号而变化。为了解决 Unicode 如何在网络上传输,互联网上使用最最广泛。在这种编码下,例如一个中文字符占 3 个字节,一个英文字符占 1 个字节,一个 Emoji 表情符号占用 4 个字节,所以说它是变长的。UnicodeUTF-8 编码之间需要经过转换,加密/解密。UTF-8Unicode 中的第0-127位字符使用 1 个字节表示,和 ASCII 编码相同,从128号开始的字符使用234位字节来表示。一个字节就是一个代码单元(code unit),一个代码点(code point)可能由1-4个代码单元组成。
  • UTF-16可变长编码,一个字符使用使用 2 个或者 4 个字节来存储。在 Java 中的字符编码实现就是采用 UTF-16 编码,一个 char 占用 2 个字节,这意味着 Java 中的一个 char 只能表示0000~FFFF的范围的 Unicode 字符,而对于10000~10FFFF范围的 Unicode 字符则需要用两个 char 才能表示(使用一个String)。(如果是前者范围二者不需要编码转换,如果是后者范围二者需要编码转换)
  • UTF-32定长编码,一个字符采用 4 个字节存储。由于 4 字节表示的范围完全容纳了 Unicode 范围,所以 UTF-32Unicode 之间不需要转换,而 UTF-8UTF-16Unicode 之间都是需要经过转换的

Java 中 char 的字符编码

Java语言规范规定: Java 的 char 类型是 UTF-16 的 code unit,占用 2 字节(也就是16位),然后字符串是 UTF-16 code unit 的序列。

UTF-16 是一种存储 Unicode 编码的具体实现,Unicode 是字符集,和 ASCII 码一样,

下表是 Java 中的 char 表示不同字符集时的区别:

Java 中 char 和 Unicode、UTF-8、UTF-16、ASCII、GBK 的关系-LMLPHP

Java 中可以使用如下代码打印一个中文汉字的16进制表示:

char c = '中';
System.out.println(Integer.toHexString(c));  // 4e2d

输出是 4e2d,这表明 Java 中使用char表示一个字符确实是占 2 个字节。

Java 中 char 和 Unicode、UTF-8、UTF-16、ASCII、GBK 的关系-LMLPHP

而对于表情符号,它需要占用 4 个字节,所以在 Java 中你是无法使用一个char来表示一个表情符号的,如果你尝试这么做会得到一个类似下面这样的编译器报错提示:

Java 中 char 和 Unicode、UTF-8、UTF-16、ASCII、GBK 的关系-LMLPHP

如何打印这个表情符号的十六进制编码呢?既然char存不下,只能使用String来存储了,我们可以使用 String.getBytes() 方法指定编码字符集来输出对应的bytes数组,例如对于汉字“中”我们可以这样打印:

try {
   
     byte[] bytes = "中".getBytes("UTF-16");
     System.out.println(bytes.length);
     for (byte b : bytes) {
   
        	int temp = b & 0xFF; // 取低8位,即1个字节
            System.out.print(String.format("%02X", temp));
            System.out.print(" "); 
       }
     System.out.println();
 } catch (UnsupportedEncodingException e) {
   
 }

输出:

4
FE FF 4E 2D 

对于表情符号类似的:

try {
   
     byte[] bytes = 
12-12 14:27