代码改变世界

【JAVA编码专题】总结

2015-02-11 15:11  jediael  阅读(158)  评论(0编辑  收藏  举报


第一部分:编码基础

为什么需要编码:用计算机看得懂的语言(二进制数)表示各种各样的字符。

一、基本概念
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中的编码