第一部分:编码基础
为什么需要编码:用计算机看得懂的语言(二进制数)表示各种各样的字符。
一、基本概念
ASCII、Unicode、big5、GBK等为字符集,它们只定义了这个字符集内有哪些字符,以及分别用什么数字表示。
而UTF-8与UTF-16则定义了Unicode字符集如何使用计算机看得懂的语言进行传输和保存。
例如: Unicode 字符 U+00A9 = 1010 1001 (版权符号) 在 UTF-8 里的编码为:
11000010 10101001 = 0xC2 0xA9
事实上,没有必要将它们严格区分,一些字符集本身就是编码方式,如ASCII。它们均表示如何用二进制数表示一个字符。
因此很多地方将UTF-8、UTF-16与ASCII、GBK等统一当作字符编码方式。
二、常见编码
明白了各种语言需要交流,经过翻译是必要的,那又如何来翻译呢?计算中提拱了多种翻译方式,常见的有 ASCII、ISO-8859-1、GB2312、GBK、UTF-8、UTF-16 等。它们都可以被看作为字典,它们规定了转化的规则,按照这个规则就可以让计算机正确的表示我们的字符。目前的编码格式很多,例如 GB2312、GBK、UTF-8、UTF-16 这几种格式都可以表示一个汉字,那我们到底选择哪种编码格式来存储汉字呢?这就要考虑到其它因素了,是存储空间重要还是编码的效率重要。根据这些因素来正确选择编码格式,下面简要介绍一下这几种编码格式。
ASCII 码
学过计算机的人都知道 ASCII 码,总共有 128 个,用一个字节的低 7 位表示,0~31 是控制字符如换行回车删除等;32~126 是打印字符,可以通过键盘输入并且能够显示出来。
ISO-8859-1
128 个字符显然是不够用的,于是 ISO 组织在 ASCII 码基础上又制定了一些列标准用来扩展 ASCII 编码,它们是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵盖了大多数西欧语言字符,所有应用的最广泛。ISO-8859-1 仍然是单字节编码,它总共能表示 256 个字符。
GB2312
它的全称是《信息交换用汉字编码字符集 基本集》,它是双字节编码,总的编码范围是 A1-F7,其中从 A1-A9 是符号区,总共包含 682 个符号,从 B0-F7 是汉字区,包含 6763 个汉字。
GBK
全称叫《汉字内码扩展规范》,是国家技术监督局为 windows95 所制定的新的汉字内码规范,它的出现是为了扩展 GB2312,加入更多的汉字,它的编码范围是 8140~FEFE(去掉 XX7F)总共有 23940 个码位,它能表示 21003 个汉字,它的编码是和 GB2312 兼容的,也就是说用 GB2312 编码的汉字可以用 GBK 来解码,并且不会有乱码。
GB18030
全称是《信息交换用汉字编码字符集》,是我国的强制标准,它可能是单字节、双字节或者四字节编码,它的编码与 GB2312 编码兼容,这个虽然是国家标准,但是实际应用系统中使用的并不广泛。
UTF-16
说到 UTF 必须要提到 Unicode(Universal Code 统一码),ISO 试图想创建一个全新的超语言字典,世界上所有的语言都可以通过这本字典来相互翻译。可想而知这个字典是多么的复杂,关于 Unicode 的详细规范可以参考相应文档。Unicode 是 Java 和 XML 的基础,下面详细介绍 Unicode 在计算机中的存储形式。
UTF-16 具体定义了 Unicode 字符在计算机中存取方法。UTF-16 用两个字节来表示 Unicode 转化格式,这个是定长的表示方法,不论什么字符都可以用两个字节表示,两个字节是 16 个 bit,所以叫 UTF-16。UTF-16 表示字符非常方便,每两个字节表示一个字符,这个在字符串操作时就大大简化了操作,这也是 Java 以 UTF-16 作为内存的字符存储格式的一个很重要的原因。
UTF-8
UTF16固定使用2个字节(或者4个字节)来表示字符,这导致与早期大量使用的ASCII码无法兼容,同时一些特殊字符在UNIX系统中存在特殊含义,如 '/0' 或 '/', 它们在 文件名和其他 C 库函数参数里都有特别的含义。此外,一些最常用的字符(西欧字符)只用一个字节就可以表示,若使用UTF16则浪费带宽或者存储空间。
三、 UTF-8详细介绍
首先 UCS 和 Unicode 只是分配整数给字符的编码表. 现在存在好几种将一串字符表示为一串字节的方法. 最显而易见的两种方法是将 Unicode 文本存储为 2 个 或 4 个字节序列的串. 这两种方法的正式名称分别为 UCS-2 和 UCS-4. 除非另外指定, 否则大多数的字节都是这样的(Bigendian convention). 将一个 ASCII 或 Latin-1 的文件转换成 UCS-2 只需简单地在每个 ASCII 字节前插入 0x00. 如果要转换成 UCS-4, 则必须在每个 ASCII
字节前插入三个 0x00.
在 Unix 下使用 UCS-2 (或 UCS-4) 会导致非常严重的问题. 用这些编码的字符串会包含一些特殊的字符, 比如 '/0' 或 '/', 它们在 文件名和其他 C 库函数参数里都有特别的含义. 另外, 大多数使用 ASCII 文件的 UNIX 下的工具, 如果不进行重大修改是无法读取 16 位的字符的. 基于这些原因, 在文件名, 文本文件, 环境变量等地方, UCS-2 不适合作为 Unicode 的外部编码.
在 ISO 10646-1 Annex R 和 RFC 2279 里定义的 UTF-8 编码没有这些问题. 它是在 Unix 风格的操作系统下使用 Unicode 的明显的方法.
UTF-8 有一下特性:
UCS 字符 U+0000 到 U+007F (ASCII) 被编码为字节 0x00 到 0x7F (ASCII 兼容). 这意味着只包含 7 位 ASCII 字符的文件在 ASCII 和 UTF-8 两种编码方式下是一样的.
所有 >U+007F 的 UCS 字符被编码为一个多个字节的串, 每个字节都有标记位集. 因此, ASCII 字节 (0x00-0x7F) 不可能作为任何其他字符的一部分.
表示非 ASCII 字符的多字节串的第一个字节总是在 0xC0 到 0xFD 的范围里, 并指出这个字符包含多少个字节. 多字节串的其余字节都在 0x80 到 0xBF 范围里. 这使得重新同步非常容易, 并使编码无国界, 且很少受丢失字节的影响.
可以编入所有可能的 231个 UCS 代码
UTF-8 编码字符理论上可以最多到 6 个字节长, 然而 16 位 BMP 字符最多只用到 3 字节长.
Bigendian UCS-4 字节串的排列顺序是预定的.
字节 0xFE 和 0xFF 在 UTF-8 编码中从未用到.
下列字节串用来表示一个字符. 用到哪个串取决于该字符在 Unicode 中的序号.
U-00000000 - U-0000007F: 0xxxxxxx
U-00000080 - U-000007FF: 110xxxxx 10xxxxxx
U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
xxx 的位置由字符编码数的二进制表示的位填入. 越靠右的 x 具有越少的特殊意义. 只用最短的那个足够表达一个字符编码数的多字节串. 注意在多字节串中, 第一个字节的开头"1"的数目就是整个串中字节的数目.
例如: Unicode 字符 U+00A9 = 1010 1001 (版权符号) 在 UTF-8 里的编码为:
11000010 10101001 = 0xC2 0xA9
而字符 U+2260 = 0010 0010 0110 0000 (不等于) 编码为:
11100010 10001001 10100000 = 0xE2 0x89 0xA0
这种编码的官方名字拼写为 UTF-8, 其中 UTF 代表 UCS Transformation Format. 请勿在任何文档中用其他名字 (比如 utf8 或 UTF_8) 来表示 UTF-8, 当然除非你指的是一个变量名而不是这种编码本身.
第二部分:JAVA编码
一、String中的编码
1、JVM的默认编码为UTF-8
System.out.println(Charset.defaultCharset());
输出:UTF-8
2、获取String的编码
可以通过public byte[] getBytes(String charsetName) throws UnsupportedEncodingException获取某个String的编码
// 二、获取String在各种编码格式中的编码
String s = "aZ中文";
// 1、默认的Unicode编码
byte[] bytesDefault = s.getBytes();
System.out.println(getHexString(bytesDefault));
// 2、也是默认的编码
byte[] bytesDefault2 = s.getBytes(Charset.defaultCharset());
System.out.println(getHexString(bytesDefault2));
// 3、UTF-8编码
byte[] bytesUTF8 = s.getBytes("UTF-8");
System.out.println(getHexString(bytesUTF8));
// 4、UTF-16编码
byte[] bytesUTF16 = s.getBytes("UTF-16");
System.out.println(getHexString(bytesUTF16));
// 5、unicode编码
byte[] bytesUnicode = s.getBytes("UNICODE");
System.out.println(getHexString(bytesUnicode));
// 6、GBK编码
byte[] bytesGBK = s.getBytes("GBK");
System.out.println(getHexString(bytesGBK));
输出:
615ae4b8ade69687
615ae4b8ade69687
615ae4b8ade69687
feff0061005a4e2d6587
feff0061005a4e2d6587
615ad6d0cec4
由此可以看出,unicode与utf16可以认为是同一方式。
3、通过byte[]编码构建String
public String(byte[] bytes, String charsetName) throws UnsupportedEncodingException
用什么方式生成的编码,就应该用原有的方式进行还原
// 三、通过byte[]编码构建String,用什么方式生成的编码,就应该用原有的方式进行还原
// 1、默认编码
String sDefault = new String(bytesDefault);
System.out.println(sDefault);
// 2、默认编码2
String sDefault2 = new String(bytesDefault2, Charset.defaultCharset());
System.out.println(sDefault2);
// 3、UTF-8编码
String sUTF8 = new String(bytesUTF8, "UTF8");
System.out.println(sUTF8);
// 4、UTF-16编码
String sUTF16 = new String(bytesUTF16, "UTF16");
System.out.println(sUTF16);
// 5、unicode编码
String sUnicode = new String(bytesUnicode, "Unicode");
System.out.println(sUnicode);
// 6、GBK编码
String sGBK = new String(bytesGBK, "GBK");
System.out.println(sGBK);
输出:
aZ中文
aZ中文
aZ中文
aZ中文
aZ中文
aZ中文
若用其它编码格式来构建String,则出现乱码
// 若通过其它编码方式进行还原,则出现乱码
// 1、用UTF-16编码解码UTF8生成的字节
String sUTF16Erro = new String(bytesDefault, "UTF16");
System.out.println(sUTF16Erro);
// 2、用unicode编码解码UTF8生成的字节
String sUnicodeError = new String(bytesDefault, "Unicode");
System.out.println(sUnicodeError);
// 3、用GBK编码解码UTF8生成的字节
String sGBKError = new String(bytesDefault, "GBK");
System.out.println(sGBKError);
输出:
慚귦隇
慚귦隇
aZ涓枃
附完整代码及输出
package org.ljh.javademo.encode;
import java.io.IOException;
import java.nio.charset.Charset;
public class DefaultEncode {
public static void main(String[] args) throws IOException {
// 一、获取当前JVM环境的默认编码
System.out.println(Charset.defaultCharset());
// 二、获取String在各种编码格式中的编码
String s = "aZ中文";
// 1、默认的Unicode编码
byte[] bytesDefault = s.getBytes();
System.out.println(getHexString(bytesDefault));
// 2、也是默认的编码
byte[] bytesDefault2 = s.getBytes(Charset.defaultCharset());
System.out.println(getHexString(bytesDefault2));
// 3、UTF-8编码
byte[] bytesUTF8 = s.getBytes("UTF-8");
System.out.println(getHexString(bytesUTF8));
// 4、UTF-16编码
byte[] bytesUTF16 = s.getBytes("UTF-16");
System.out.println(getHexString(bytesUTF16));
// 5、unicode编码
byte[] bytesUnicode = s.getBytes("UNICODE");
System.out.println(getHexString(bytesUnicode));
// 6、GBK编码
byte[] bytesGBK = s.getBytes("GBK");
System.out.println(getHexString(bytesGBK));
// 三、通过byte[]编码构建String,用什么方式生成的编码,就应该用原有的方式进行还原
// 1、默认编码
String sDefault = new String(bytesDefault);
System.out.println(sDefault);
// 2、默认编码2
String sDefault2 = new String(bytesDefault2, Charset.defaultCharset());
System.out.println(sDefault2);
// 3、UTF-8编码
String sUTF8 = new String(bytesUTF8, "UTF8");
System.out.println(sUTF8);
// 4、UTF-16编码
String sUTF16 = new String(bytesUTF16, "UTF16");
System.out.println(sUTF16);
// 5、unicode编码
String sUnicode = new String(bytesUnicode, "Unicode");
System.out.println(sUnicode);
// 6、GBK编码
String sGBK = new String(bytesGBK, "GBK");
System.out.println(sGBK);
// 若通过其它编码方式进行还原,则出现乱码
// 1、用UTF-16编码解码UTF8生成的字节
String sUTF16Erro = new String(bytesDefault, "UTF16");
System.out.println(sUTF16Erro);
// 2、用unicode编码解码UTF8生成的字节
String sUnicodeError = new String(bytesDefault, "Unicode");
System.out.println(sUnicodeError);
// 3、用GBK编码解码UTF8生成的字节
String sGBKError = new String(bytesDefault, "GBK");
System.out.println(sGBKError);
}
// 输入byte[],将之转化为16进制的字符进行输出,如输入{90,20,21},则返回5A1415。因为默认情况下byte以10进制格式进行输出
private static String getHexString(byte[] b) {
String hexs = "";
for (int i = 0; i < b.length; i++) {
String hex = Integer.toHexString(b[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
hexs += hex;
}
return hexs;
}
}
输出:
UTF-8
615ae4b8ade69687
615ae4b8ade69687
615ae4b8ade69687
feff0061005a4e2d6587
feff0061005a4e2d6587
615ad6d0cec4
aZ中文
aZ中文
aZ中文
aZ中文
aZ中文
aZ中文
慚귦隇
慚귦隇
aZ涓枃
二、JAVA IO中的编码