Java 7之基础 - 编码与解码

时间:2022-10-17 21:07:35

一般涉及到编码和解码的地方是在字符和字节相互转换的时候,一般应用的主要场景在I/O这块。如果解决不好转换的问题,会碰到各种各样的问题,最常见的就是乱码。下面来深入探究一下Java中的编码与解码。

首先来看一下常见的码表,如下:

ASCII: 美国标准信息交换码。用一个字节7位可以表示
ISO8859-1 拉丁码表。欧洲码表用一个字节8位表示
GB2312 中国的中文编码表
GBK 中国的中文编码表升级,融合了更多的中文文字符号
Unicode 国际标准码,融合了多种文字。所有文字都用两个字节来表示,Java语言就是unicode
UTF-8 最多用三个字节来表示一个字符

在Java程序运行过程中,字符串对象始终以Unicode编码方式保存在内存中,但将字符串对象保存到持久化资源(文件或数据库)或将其通过网络传输时,通常是以字节的方式进行处理。这样就要求Java API必须提供两者互换的功能。事实上这一功能在String类及Charset类中已经提供。


1、String类


      利用String类的getBytes()方法返回不同字符集的字节流数据,其本质是从Unicode字符集编码向其它字符集编码转换的过程

String str="中";
byte[] bytes1 = str.getBytes(); // 1
byte[] bytes2 = str.getBytes("ISO-8859-1"); // 2
System.out.println(new String(bytes1)); // 正常显示中
System.out.println(new String(bytes2)); // 输出"?"
       把一个字符串“中”赋给 String 类的一个对象 str,这个字符串“中”是按照操作系统默认编码方式进行编码,在中文 windows 系统中通常是“GBK”,“中”在GBK编码中是0xD6D0,在将该字符赋给str时,Java会对该字符串进行编码转换,即将GBK编码方式的“中”转换成Unicode编码方式的“中”,Unicode编码方式“中”的编码是0x4E2D,所以str在程序运行期间在内存中的二进制表示成16进制就是0x4E2D。

       获得str字符串的二进制形式。getBytes(String encoding)方法需要指定编码方式,表示获得该字符串在何种编码方式中的二进制形式。此语句中没有设置参数,表示采用操作系统默认的编码方式,所以是GBK编码的二进制形式,即bytes[0]=0xD6, bytes[1]=0xD0(中的GBK编码为0xD6D0)。由于GBK编码也支持中文,所以输出时不会显示为乱码。

       在指定了编码方式为ISO-8859-1,即通常所说的Latin-1时,该编码采用8bit对字符编码,所以编码空间中只有256个字符。该编码中只包含了基本的ASCII码和一些扩展的其它西欧字符,所以该字符集中不可能包含中文的“中”字,也就是说Java虚拟机无法在ISO-8859-1编码集中找到“中”字对应的编码,针对这种情况,就只返回一个问号(?,0x3f)字符,所以此时bytes.length只有1,且bytes[0]=0x3f。

再来看一个例子:

public static void main(String[] args) {  

String str = "中国";
printBytes("UNICODE编码:", str.getBytes(Charset.forName("unicode")));
printBytes("GBK编码 :", str.getBytes(Charset.forName("GBK")));
printBytes("UTF-8编码:", str.getBytes(Charset.forName("UTF-8")));
}

public static void printBytes(String title, byte[] data) {
System.out.println(title);
for (byte b : data) {
System.out.print("0x" + toHexString(b) + " ");
}
System.out.println();
}

public static String toHexString(byte value) {
String tmp = Integer.toHexString(value & 0xFF);
if (tmp.length() == 1) {
tmp = "0" + tmp;
}

return tmp.toUpperCase();
}
输出结果如下:

中国的UNICODE编码:
0xFE 0xFF 0x4E 0x2D 0x56 0xFD 
中国的GBK编码:
0xD6 0xD0 0xB9 0xFA 
中国的UTF-8编码:
0xE4 0xB8 0xAD 0xE5 0x9B 0xBD

需要注意的是,从字符串对象中取出的Unicode编码的字节流数据时,其开始部分存在一个BOM(ByteOrderMark),一般情况下,该BOM值为“0xFE 0xFF”,即大端字节序(BIG_ENDIAN)。如果BOM值为“0xFF 0xFE”则为小端字节序(LITTLE_ENDIAN)。


也可以利用String类的构造方法根据不同字符集的字节流数据产生一个字符串对象, 其本质是从其它字符集编码向Unicode字符集编码转换的过程常见形式如下:

new String(byte[] bytes, String encoding)
来看一个例子:

byte[] bytes = {(byte)0xD6, (byte)0xD0, (byte)0x31};
String str1 = new String(bytes);
String str2= new String(bytes,"ISO-8859-1");
System.out.println(str1); // 中1
System.out.println(str2); // ??1

     该方法按照encoding编码方法对字节数组bytes中的二进制数组进行解析,生成一个新的字符串对象。
     将该字节数组中的二进制数据按照默认的编码方式(GBK)编码成字符串,我们知道GBK中0xD6 0xD0表示“中”,0x31表示字符“1”(GBK兼容ASCII,但不兼容ISO-8859-1除ASCII之外的部分),所以str1得到的值是“中1”

     该句用ISO-8859-1编码方式对该字节数据进行编码,由于在ISO-8859-1编码方式中一个字节会被解析成一个字符,所以该字节数组会被解释成包含三个字符的字符串,但由于在ISO-8859-1编码方式中没有对应0xD6和0xD0的字符,所以前两个字符会产生两个问号,由于0x31在ISO-8859-1编码中对应字符“1”(ISO-8859-1也兼容ASCII),所以此语句得到str的值是“??1”。


2、Charset类


现在的Java类中提供了Charset 类,这个类中提供 encode 与 decode 分别对应 char[] 到 byte[] 的编码和 byte[] 到 char[] 的解码。如下代码所示:

Charset charset = Charset.forName("UTF-8"); 
ByteBuffer byteBuffer = charset.encode(string);
CharBuffer charBuffer = charset.decode(byteBuffer);
编码与解码都在一个类中完成,通过 forName()方法设置编解码字符集,这样更容易统一编码格式。

Java 中还有一个 ByteBuffer 类,它提供一种 char 和 byte 之间的软转换,它们之间转换不需要编码与解码,只是把一个 16bit 的 char 格式,拆分成为 2 个 8bit 的 byte 表示,它们的实际值并没有被修改,仅仅是数据的类型做了转换。如下代码所以:

ByteBuffer heapByteBuffer = ByteBuffer.allocate(1024); 
ByteBuffer byteBuffer = heapByteBuffer.putChar(c);

以上这些提供字符和字节之间的相互转换只要我们设置编解码格式统一一般都不会出现问题。



3、总结


使用String类时:


编码:字符串变成字节数组;String -->byte[];  (String类的方法):str.getBytes(charsetName);;将该字符串按照指定编码表编码。
解码:字节数组变成字符串;byte[] -->String;  (String类的构造方法):new String(byte[],charsetName);通过使用指定的charset解码指定的 byte 数组。


使用Charset类时:
类中提供 encode 与 decode 分别对应 char[] 到 byte[] 的编码和 byte[] 到 char[] 的解码。首先通过forName()来设置编码和解码字符集。












参考文献:

1、http://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/

2、http://blog.sina.com.cn/s/blog_6ab818cb0100qis5.html