本文介绍字符编码转换问题。
基本概念
编码过程,字符串 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