乱码原因
字符在保存时的编码格式如果和要显示的编码格式不一样的话,就会出现乱码问题。Java在运行期一律以Unicode
来存储字符,这样有利的支持了多语言环境。Java读文件的时候会用到系统默认的编码来解码文件。所以在用FileInputStream
类读取文件可以指定编码读取。
JAVA
在网络传输中使用的编码是ISO-8859-1
,故在输出时需要进行转化,如:
|
|
经过网络编码后的中文,要正确显示在页面上必须要用类似于:
|
|
案例
浏览器请求服务,服务器返回响应
|
|
request
请求过程:
- 浏览器
http
响应增加一个Content-Type:text/html; charset=utf-8
,发送请求 Tomcat
接受请求(不设置URIEncodng
)java
服务端处理请求
|
|
编码转换过程:
- 浏览器对字符做百分号编码
Tomcat
解百分号编码ISO8859-1
编码转Java
内码Java
内码转ISO8859-1
编码- 把字节数组当成
utf-8
编码转Java
内码 Java
内码转输出编码注:
|
|
浏览器对字符做百分号编码
对于[中国]这两个字符,他们的utf-8
编码分别是0xE4B8AD
、0xE59BBD
,每个字符占用三个字节。经过百分号编码后变成了%E4%B8%AD
、%E5%9B%BD
,可以看到百分号编码对原始编码是无损的,它只是把原始字节变成了%+原始字节的16进制表示。比如字节0xE4
,转成百分号编码为%E4
,有一个字节变成了三个字节。
Tomcat解码百分号编码
解码百分号编码也很简单,其实就是去掉百分号,然后将百分号后的两个字节合并成一个字节,如百分号编码%E4
,解码后变为字节0xE4
。到这一步“中国”这两个字符就变成了0xE4B8AD
、0xE59BBD
。
ISO8859-1转java内码
ISO8859-1
与ascii
一样都是单字节字符集,不同的是它把最高位利用起来了,增加了一些西方字符(如±、÷等字符)
这里说的java
内码是java
程序运行时,在内存中存储字符的编码,用的是unicode
标准中定义的utf-16
编码
utf-16
是把unicode
字符编码成2字节或4字节。ISO8859-1
是8位长的单字节字符编码,所以utf-16
编码和ISO8859-1
编码是不兼容的,但是utf-16包含ISO8859-1中的所有字符.
Tomcat解码百分号编码后,[中国]这两个字符在内存中是这样的0xE4B8ADE59BBD
,正好六个字节。这其实是这两个字符的utf-8
编码序列,但是由于并没有告诉tomcat
这是什么字符编码序列,所以tomcat
就认为这是一个ISO8859-1
编码序列,并把它告诉了java程序,java程序要做的就是把这个字节序列按照ISO8859-1
转换成utf-16
,转换成功后的对应关系是这样的:
ISO8859-1 | 0xE4 | 0xB8 | 0xAD | 0xE5 | 0x9B | 0xBD |
---|---|---|---|---|---|---|
UTF-16 | 0x00E4 | 0x00B8 | 0x00AD | 0x00E5 | 0x009B | 0x00BD |
可以看到原本的两个字符,在java中变成了六个字符;原本的六个字节,在java中变成了12个字节。
Java内码转换成ISO8859-1编码
|
|
getBytes(“iso8859-1”)
这个方法,也就是把utf-16
转换成ISO8859-1
。有第三步(ISO8859-1转java内码)中的对应表格可以看到,utf-16
转ISO8859-1
只需要把每个字符前面的8位0去掉就可以了,转换成功后俩个字符就又变成了0xE4B8ADE59BBD
。虽然两次转换过程中,对字节的解释是错误的,但是并没有丢失原始字节信息。
把字节数组当成utf-8编码转java内码
|
|
因为字节数组本来就是utf-8编码,所以按照utf-8来转码肯定是没问题的,转换成功后的对应关系是这一样的:
UTF-8 | 0xE4B8AD | 0xE59BBD |
---|---|---|
UTF-16 | 0x4E2D | 0x56FD |
到这里[中国]这两个字符在java内部才得到了正确的表示。
Java内码转输出编码
|
|
现在[中国]这两个字符在java内部用utf-16
得到了正确的表示,剩下的最后一步就是对外输出,也就是对外翻译的过程,我们这里用的java自带的println
方法,这个方法会根据当前平台的自身编码进行输出,比如你的平台环境是中文,那输出的可能就是GBK
编码。