字符与编码
人类使用的是字符,计算机存储的是比特,要想在计算机中存储字符就必须进行编码。从字符到比特序列的过程为编码,比特序列到字符的过程为解码。
字符集与编码格式
字符集是字符的集合。可以认为是一个数字到字符的映射表,比如1对应‘大’,2对应‘家’,3对应‘好’,那么“大家好”编码后就为1 2 3,1 2 3解码后就是“大家好”,只要根据这张表就能完成编解码。
编码格式是字符集的具体实现,如上面的字符集,1、2、3具体怎么存储在计算机中呢,用一个字节吗?如果此字符集有5000个字符,怎么存储呢?按照简单可以直接每个字符表示为两个字节,还可以利用压缩的原理,将使用频率最高的128个字符编号为0-127,然后存储在一个字节中,其他的用2个字节,这样可以提高存储效率。这两种具体实现就是同一种字符集的两种编码格式。
字符集与编码格式是一对多关系,可以认为是抽象和实现的关系。
常见编码格式
ASCII
美国信息交换标准代码,用于显示现代英语。也是大家最熟悉的编码格式,编码范围0x00-0x7F,每个字符存储1个字节。
ascii表.png
ISO-8859-1
它以ASCII为基础,在空置的0xA0-0xFF的范围内,加入96个字母及符号,以适应西欧国家的使用,在西欧非常流行。
GBK
中文常用编码格式,在ascii码的基础上加入2w多个汉字,每个汉字占两个字节。
上述这些即是字符集也是编码格式,Unicode字符集拥有多种编码格式。
Unicode
每种语言都有自己的字符集,这让互联网上的交流充满了乱码,为了使世界上大部分字符统一字符集,于是Unicode就诞生了。Unicode包含了10w多个字符,达到了字符集统一的目的。Unicode拥有多种编码格式。
UTF-8
Unicode的一种编码格式,主要针对存储、传输设计,每个字符用1-4个字节表示,节省存储空间,广泛应用与网络通信。
UTF-16
Unicode的另一种编码格式,每个字符用两个字节表示,后来加入了一些新字符后有一些字符需要用多个字符表示。
Unicode的常用编码格式的比较
unicode不同编码格式.jpg编码的兼容性与不可逆性
字符在编码和解码时必须使用相同的编码格式才能还原,否则可能出现乱码。编码后的是字节流,要想解码后得到原字符,则必须保证解码格式按此字节流能恢复成原字符,也就是编码格式要能兼容编码格式。
如果编码后的字节流在解码的编码格式中此字节流有效,就会出现乱码但可逆,无效且更改了字节流则会出现乱码并且不可逆。
上面的常见编码格式中,除了UTF-16不兼容ASCII,其他的都兼容。汉字编码格式。
Java中的编码
java中各阶段编码格式要求:
java编码概括.jpg
可以看出Java源文件不限编码格式,Class字节码文件采用UTF-8编码格式,在虚拟机内存中采用UTF-16编码,输出的时候按照需要获得各种编码格式。Class文件采用UTF-8编码是因为UTF-8存储效率更高,适合存储与传输,运行时采用UTF-16是因为以前UTF-16还是定长编码,读取效率更高,现在这个优势已经消失了。
JVM外乱码
这类乱码出现在jvm运行时之前,具体来说是编译阶段中的输入阶段。
从Java源码文件到Java Class文件,中间会经过Java源码编译器(例如javac或ECJ)的编译。
也就是说,是Java源码编译器负责将Java源码文件的编码转换为最终的UTF-8。
导致乱码的不是Java源码编译器的“编码”(写出UTF-8)的过程,而是“解码”(读入Java源码内容)的过程。
这类乱码只需要将源码格式统一为UTF-8一般就能避免。
JVM运行时乱码
在java程序运行时对字符的编码解码所用的编码格式不兼容所导致的乱码现象,这也是主要的乱码原因。
Java 中String的编码转换
首先需要说明的是,Java中的String都是UTF-16编码的。
看String的两个方法:
public byte[] getBytes()
Encodes this String into a sequence of bytes using the platform's default charset, storing the result into a new byte array.
new String(byte[], charsetName)
Constructs a new String by decoding the specified array of bytes using the specified charset. The length of the new String is a function of the charset, and hence may not be equal to the length of the byte array.
getBytes()
就是编码过程,将字符按照编码格式转换成字节流,String(byte[], charsetName)
就是将byte字节流按照charsetName指定的编码格式转换成字符。注意charsetName是指byte[]字节流的编码格式,而不是把byte[]转换成charsetName编码。
默认编码格式
getBytes()
不传字符集参数时是按操作系统的默认编码格式获取字节流。
默认编码格式:
System.out.println(Charset.defaultCharset());
可能是UTF-8、GBK等,取决于操作系统,并且是jvm已启动就确定的,不可更改。
编码转换
字符串编码转换函数:
/**
* 将一个字符串由一种编码格式转换到另一种编码格式
* @param oldStr 待转换的字符串
* @param oldCharset oldStr对应的编码格式,null表示用默认编码格式
* @param newCharset 新的编码格式,null表示用默认编码格式
* @return 新的字符串
* @throws UnsupportedEncodingException
*/
String changeCharset(String oldStr,String oldCharset,String newCharset) throws UnsupportedEncodingException {
if(oldCharset==null)
oldCharset=Charset.defaultCharset().name();
if(newCharset==null)
newCharset=Charset.defaultCharset().name();
return new String(oldStr.getBytes(oldCharset), newCharset);
}
乱码示例:
@Test
public void defaultChartTest() throws UnsupportedEncodingException {
System.out.println("默认字符集:"+Charset.defaultCharset().name());
String s="你好,大家好";
String sIso=changeCharset(s,null,"ISO-8859-1");
System.out.println(sIso);
String sDef=changeCharset(sIso,"ISO-8859-1",null);
System.out.println(sDef);
}
输出:
默认字符集:UTF-8
ä½ å¥½,大家好
你好,大家好
本人机器的默认字符集是UTF-8,首先取得字符串的UTF-8编码的字节流,然后按照ISO-8859-1进行解码,两者不兼容所以得到的是乱码,要想将此乱码恢复,必须按照ISO-8859-1进行解码还原字节流(可能还原不成功,就是出现信息丢失,不可逆),因为此字节流的有意义的编码是UTF-8,所以按照UTF-8解码就能得到原字符串。
总的来说,注意编码格式和解码格式保持一致,就能避免乱码问题,重点在字符集、编码格式和乱码产生的原因。
参考资料
1.java编译器编码和JVM编码问题
2.深入分析 Java 中的中文编码问题
3.Java编码浅析(注意区分三个概念)
4.Java字符编码根本原理
网友评论