JAVA中字符编码研究

本文介绍字符编码转换问题。


基本概念

编码过程,字符串 strA 经过某个字符集 (charset) 编码后成二进制数据 (byte[]  binA)。 对应java 中就是: byte[] binA = strA.getBytes(charset).

解码过程,二进制数据 byte[] binA,经过字符集(charset)解码,还原成字符串strA。对应java中就是: String strA = new String(binA, charset).


字符串到字符串的还原


我们知道, strA 经过 charset1  编码成 binA, binA需要使用同样的 charset1解码才能还原出原来的strA,否则就会出现乱码。


	        String asciiStr = "ABC";
		String cnStr = "中国人";
		
		System.out.println("defaultCharset:" + Charset.defaultCharset());
		
		// 使用默认字符集编码
		byte[] asciiStrBytes = asciiStr.getBytes();
		byte[] cnStrBytes = cnStr.getBytes();

		System.out.println(asciiStr + " 编码后:" + showBytes(asciiStrBytes));
		System.out.println(cnStr + " 编码后:" + showBytes(cnStrBytes));

		// 使用默认字符集解码
		String decodedAsciiStr = new String(asciiStrBytes);
		String decodedcnStr = new String(cnStrBytes);

		System.out.println(showBytes(asciiStrBytes) + ", 解码后:'" + decodedAsciiStr + "'");
		System.out.println(showBytes(cnStrBytes) + ", 解码后:'" + decodedcnStr + "'");


在我机器上输出为:


defaultCharset:UTF-8
ABC 编码后:01000001_01000010_01000011
中国人 编码后:11100100_10111000_10101101_11100101_10011011_10111101_11100100_10111010_10111010
01000001_01000010_01000011, 解码后:'ABC'
11100100_10111000_10101101_11100101_10011011_10111101_11100100_10111010_10111010, 解码后:'中国人'

字符串 ‘ABC’ 和‘中国人’ 经过我机器上默认的(UTF-8)编码方式,先编码,再解码。字符串解码后成功还原。


如果是用 另外的字符集 (如iso8859-1)解码 UTF-8 编码的数据,就会出现乱码:


                // 使用iso8859-1解码二进制数据
		decodedAsciiStr = new String(asciiStrBytes, "iso8859-1");
		decodedcnStr = new String(cnStrBytes, "iso8859-1");

		System.out.println(showBytes(asciiStrBytes) + ", 经过 iso8859-1 解码后:" + decodedAsciiStr);
		System.out.println(showBytes(cnStrBytes) + ", 经过 iso8859-1 解码后:" + decodedcnStr);

输出:

01000001_01000010_01000011, 经过 iso8859-1 解码后:ABC
11100100_10111000_10101101_11100101_10011011_10111101_11100100_10111010_10111010, 经过 iso8859-1 解码后:中国人

上面 ‘ABC’ 成功还原,然而,”中国人“ 出现了乱码。这是因为 iso8859-1和 UTF-8 对ASCCII字符是兼容的,对汉字则不兼容。


二进制数据到二进制数据的还原


现在思考一个问题:

我们知道,字符串strA 经过charset1编码后,再经过charset1解码,就能还原字符串strA;那么二进制数据 binA 经过 charset1 解码后,再经过charset1编码,能不能还原出原来的二进制数据binA呢?


看下面的代码, 代码中,先使用 iso8859-1对二进制数据 asciiStrBytes 和 cnStrBytes  解码,然后,再用iso8859-1对解码后的字符串编码成二进制数据 encodedAsciiStrBytes 和 encodedcnStrBytes,最后对比之前和之后的二进制数据。

	      // 使用iso8859-1解码二进制数据
		decodedAsciiStr = new String(asciiStrBytes, "iso8859-1");
		decodedcnStr = new String(cnStrBytes, "iso8859-1");

		System.out.println(showBytes(asciiStrBytes) + ", 经过 iso8859-1 解码后:" + decodedAsciiStr);
		System.out.println(showBytes(cnStrBytes) + ", 经过 iso8859-1 解码后:" + decodedcnStr);

		// 再使用 iso8859-1 编码经过 iso8859-1解码后的数据

		byte[] encodedAsciiStrBytes = decodedAsciiStr.getBytes("iso8859-1");
		byte[] encodedcnStrBytes = decodedcnStr.getBytes("iso8859-1");

		System.out.println("'" + decodedAsciiStr + "',经过 iso8859-1 编码后:" + showBytes(encodedAsciiStrBytes));
		System.out.println("'" + decodedcnStr + "',经过 iso8859-1 编码后:" + showBytes(encodedcnStrBytes));

		System.out.println(
				"asciiStrBytes == encodedAsciiStrBytes ? :" + Arrays.equals(asciiStrBytes, encodedAsciiStrBytes));
		System.out.println("cnStrBytes == encodedcnStrBytes ? :" + Arrays.equals(cnStrBytes, encodedcnStrBytes));


输出:

01000001_01000010_01000011, 经过 iso8859-1 解码后:ABC
11100100_10111000_10101101_11100101_10011011_10111101_11100100_10111010_10111010, 经过 iso8859-1 解码后:中国人
'ABC',经过 iso8859-1 编码后:01000001_01000010_01000011
'中国人',经过 iso8859-1 编码后:11100100_10111000_10101101_11100101_10011011_10111101_11100100_10111010_10111010
asciiStrBytes == encodedAsciiStrBytes ? :true
cnStrBytes == encodedcnStrBytes ? :true

可见,任意二进制数据经过 iso8859-1 解码,再经过iso8859-1编码,可以还原原来的二进制数据。


那么使用GBK呢?


看下面代码,代码中先使用GBK对二进制数据解码,再使用GBK对解码后的字符串编码成二进制数据,然后对比之前之后的二进制数据。

		// 使用GBK解码二进制数据
		decodedAsciiStr = new String(asciiStrBytes, "GBK");
		decodedcnStr = new String(cnStrBytes, "GBK");

		System.out.println(showBytes(asciiStrBytes) + ", 经过 GBK 解码后:" + decodedAsciiStr);
		System.out.println(showBytes(cnStrBytes) + ", 经过 GBK 解码后:" + decodedcnStr);

		// 再使用 GBK 编码经过 GBK 解码后的数据

		encodedAsciiStrBytes = decodedAsciiStr.getBytes("gbk");
		encodedcnStrBytes = decodedcnStr.getBytes("gbk");

		System.out.println("'" + decodedAsciiStr + "',经过 GBK 编码后:" + showBytes(encodedAsciiStrBytes));
		System.out.println("'" + decodedcnStr + "',经过 GBK 编码后:" + showBytes(encodedcnStrBytes));

		System.out.println(
				"asciiStrBytes == encodedAsciiStrBytes ? :" + Arrays.equals(asciiStrBytes, encodedAsciiStrBytes));
		System.out.println("cnStrBytes == encodedcnStrBytes ? :" + Arrays.equals(cnStrBytes, encodedcnStrBytes));

输出:

01000001_01000010_01000011, 经过 GBK 解码后:ABC
11100100_10111000_10101101_11100101_10011011_10111101_11100100_10111010_10111010, 经过 GBK 解码后:涓浗浜�
'ABC',经过 GBK 编码后:01000001_01000010_01000011
'涓浗浜�',经过 GBK 编码后:11100100_10111000_10101101_11100101_10011011_10111101_11100100_10111010_00111111
asciiStrBytes == encodedAsciiStrBytes ? :true
cnStrBytes == encodedcnStrBytes ? :false

从输出可以看出,对于二进制数据 01000001_01000010_01000011,通过GBK解码再编码,成功还原了。

但是对于另外一个二进制数据:11100100_10111000_10101101_11100101_10011011_10111101_11100100_10111010_10111010,通过GBK解码再编码得到了如下二进制数据:

11100100_10111000_10101101_11100101_10011011_10111101_11100100_10111010_00111111

不能还原。


可见,任意二进制数据经过 GBK 解码,再经过GBK编码,不能还原原来的二进制数据。


结论: 

字符串strA 经过charset1编码后,再经过charset1解码,就能还原字符串strA;

二进制数据 binA 经过iso8859-1(单字节编码方式) 解码后,再经过iso8859-1编码,能还原出二进制数据binA.

二进制数据 binA 经过像GBK这样的多字节编码方式解码后,再经过相同的字符集编码,不能还原出二进制数据binA.

所以,如果我们在网络上,通过输入流获得文本的二进制数据时,然而又不知道它的编码信息,或者知道它的编码信息但是本地系统不支持(如数据库不支持),可以是使用iso8859-1将其解码成字符串,存入本地系统(文件,或者数据库),这样原始的二进制信息不会丢失,待以后能处理它的字符编码时再处理。



完整代码:

package io;

import java.nio.charset.Charset;
import java.util.Arrays;

public class EncodeDemo {

	public static void main(String[] args) throws Exception {
		String asciiStr = "ABC";
		String cnStr = "中国人";
		
		System.out.println("defaultCharset:" + Charset.defaultCharset());
		
		// 使用默认字符集编码
		byte[] asciiStrBytes = asciiStr.getBytes();
		byte[] cnStrBytes = cnStr.getBytes();

		System.out.println(asciiStr + " 编码后:" + showBytes(asciiStrBytes));
		System.out.println(cnStr + " 编码后:" + showBytes(cnStrBytes));

		// 使用默认字符集解码
		String decodedAsciiStr = new String(asciiStrBytes);
		String decodedcnStr = new String(cnStrBytes);

		System.out.println(showBytes(asciiStrBytes) + ", 解码后:'" + decodedAsciiStr + "'");
		System.out.println(showBytes(cnStrBytes) + ", 解码后:'" + decodedcnStr + "'");

		// 使用iso8859-1解码二进制数据
		decodedAsciiStr = new String(asciiStrBytes, "iso8859-1");
		decodedcnStr = new String(cnStrBytes, "iso8859-1");

		System.out.println(showBytes(asciiStrBytes) + ", 经过 iso8859-1 解码后:" + decodedAsciiStr);
		System.out.println(showBytes(cnStrBytes) + ", 经过 iso8859-1 解码后:" + decodedcnStr);

		// 再使用 iso8859-1 编码经过 iso8859-1解码后的数据

		byte[] encodedAsciiStrBytes = decodedAsciiStr.getBytes("iso8859-1");
		byte[] encodedcnStrBytes = decodedcnStr.getBytes("iso8859-1");

		System.out.println("'" + decodedAsciiStr + "',经过 iso8859-1 编码后:" + showBytes(encodedAsciiStrBytes));
		System.out.println("'" + decodedcnStr + "',经过 iso8859-1 编码后:" + showBytes(encodedcnStrBytes));

		System.out.println(
				"asciiStrBytes == encodedAsciiStrBytes ? :" + Arrays.equals(asciiStrBytes, encodedAsciiStrBytes));
		System.out.println("cnStrBytes == encodedcnStrBytes ? :" + Arrays.equals(cnStrBytes, encodedcnStrBytes));

		// 使用GBK解码二进制数据
		decodedAsciiStr = new String(asciiStrBytes, "GBK");
		decodedcnStr = new String(cnStrBytes, "GBK");

		System.out.println(showBytes(asciiStrBytes) + ", 经过 GBK 解码后:" + decodedAsciiStr);
		System.out.println(showBytes(cnStrBytes) + ", 经过 GBK 解码后:" + decodedcnStr);

		// 再使用 GBK 编码经过 GBK 解码后的数据

		encodedAsciiStrBytes = decodedAsciiStr.getBytes("gbk");
		encodedcnStrBytes = decodedcnStr.getBytes("gbk");

		System.out.println("'" + decodedAsciiStr + "',经过 GBK 编码后:" + showBytes(encodedAsciiStrBytes));
		System.out.println("'" + decodedcnStr + "',经过 GBK 编码后:" + showBytes(encodedcnStrBytes));

		System.out.println(
				"asciiStrBytes == encodedAsciiStrBytes ? :" + Arrays.equals(asciiStrBytes, encodedAsciiStrBytes));
		System.out.println("cnStrBytes == encodedcnStrBytes ? :" + Arrays.equals(cnStrBytes, encodedcnStrBytes));

	}

	public static String showBytes(byte[] bytes) {
		StringBuilder sb = new StringBuilder();
		for (byte b : bytes) {
			String tmpBin = "00000000" + Integer.toBinaryString(Byte.toUnsignedInt(b));
			sb.append(tmpBin.substring(tmpBin.length() - 8, tmpBin.length())).append("_");
		}
		sb.deleteCharAt(sb.length() - 1);
		return sb.toString();
	}
	
}


完整输出:

defaultCharset:UTF-8
ABC 编码后:01000001_01000010_01000011
中国人 编码后:11100100_10111000_10101101_11100101_10011011_10111101_11100100_10111010_10111010
01000001_01000010_01000011, 解码后:'ABC'
11100100_10111000_10101101_11100101_10011011_10111101_11100100_10111010_10111010, 解码后:'中国人'
01000001_01000010_01000011, 经过 iso8859-1 解码后:ABC
11100100_10111000_10101101_11100101_10011011_10111101_11100100_10111010_10111010, 经过 iso8859-1 解码后:中国人
'ABC',经过 iso8859-1 编码后:01000001_01000010_01000011
'中国人',经过 iso8859-1 编码后:11100100_10111000_10101101_11100101_10011011_10111101_11100100_10111010_10111010
asciiStrBytes == encodedAsciiStrBytes ? :true
cnStrBytes == encodedcnStrBytes ? :true
01000001_01000010_01000011, 经过 GBK 解码后:ABC
11100100_10111000_10101101_11100101_10011011_10111101_11100100_10111010_10111010, 经过 GBK 解码后:涓浗浜�
'ABC',经过 GBK 编码后:01000001_01000010_01000011
'涓浗浜�',经过 GBK 编码后:11100100_10111000_10101101_11100101_10011011_10111101_11100100_10111010_00111111
asciiStrBytes == encodedAsciiStrBytes ? :true
cnStrBytes == encodedcnStrBytes ? :false


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值