中文乱码

乱码原因

字符在保存时的编码格式如果和要显示的编码格式不一样的话,就会出现乱码问题。Java在运行期一律以Unicode来存储字符,这样有利的支持了多语言环境。Java读文件的时候会用到系统默认的编码来解码文件。所以在用FileInputStream类读取文件可以指定编码读取。 

JAVA在网络传输中使用的编码是ISO-8859-1,故在输出时需要进行转化,如:

1
String str=new String(str.getBytes("开发环境编码"),"ISO-8859-1");

经过网络编码后的中文,要正确显示在页面上必须要用类似于:

1
Stirng str=new String(str.getBytes("ISO-8859-1"),"开发环境编码");

案例

浏览器请求服务,服务器返回响应

1
http request——————————>http response

request请求过程:

  • 浏览器http响应增加一个Content-Type:text/html; charset=utf-8,发送请求
  • Tomcat接受请求(不设置URIEncodng)
  • java服务端处理请求
1
2
3
4
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("name: "+newString(req.getParameter("name").getBytes("iso8859-1"),"utf-8"));
}

编码转换过程:

  • 浏览器对字符做百分号编码
  • Tomcat解百分号编码
  • ISO8859-1编码转Java内码
  • Java内码转ISO8859-1编码
  • 把字节数组当成utf-8编码转Java内码
  • Java内码转输出编码

    注:

1
2
String.getBytes("utf-8"): 把java内码转成utf-8编码
new String(bytes[],"utf-8"): 把字节数组当成utf-8编码转成java内码

浏览器对字符做百分号编码

对于[中国]这两个字符,他们的utf-8编码分别是0xE4B8AD0xE59BBD,每个字符占用三个字节。经过百分号编码后变成了%E4%B8%AD%E5%9B%BD,可以看到百分号编码对原始编码是无损的,它只是把原始字节变成了%+原始字节的16进制表示。比如字节0xE4,转成百分号编码为%E4,有一个字节变成了三个字节。

Tomcat解码百分号编码

解码百分号编码也很简单,其实就是去掉百分号,然后将百分号后的两个字节合并成一个字节,如百分号编码%E4,解码后变为字节0xE4。到这一步“中国”这两个字符就变成了0xE4B8AD0xE59BBD

ISO8859-1转java内码

ISO8859-1ascii一样都是单字节字符集,不同的是它把最高位利用起来了,增加了一些西方字符(如±、÷等字符)

这里说的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-10xE40xB80xAD0xE50x9B0xBD
UTF-160x00E40x00B80x00AD0x00E50x009B0x00BD

可以看到原本的两个字符,在java中变成了六个字符;原本的六个字节,在java中变成了12个字节。

Java内码转换成ISO8859-1编码

1
req.getParameter("name").getBytes("iso8859-1")

getBytes(“iso8859-1”)这个方法,也就是把utf-16转换成ISO8859-1。有第三步(ISO8859-1转java内码)中的对应表格可以看到,utf-16ISO8859-1只需要把每个字符前面的8位0去掉就可以了,转换成功后俩个字符就又变成了0xE4B8ADE59BBD。虽然两次转换过程中,对字节的解释是错误的,但是并没有丢失原始字节信息。

把字节数组当成utf-8编码转java内码

1
new String(0xE4B8ADE59BBD,"utf-8")

因为字节数组本来就是utf-8编码,所以按照utf-8来转码肯定是没问题的,转换成功后的对应关系是这一样的:

UTF-80xE4B8AD0xE59BBD
UTF-160x4E2D0x56FD

到这里[中国]这两个字符在java内部才得到了正确的表示。

Java内码转输出编码

1
System.out.println("中国")

现在[中国]这两个字符在java内部用utf-16得到了正确的表示,剩下的最后一步就是对外输出,也就是对外翻译的过程,我们这里用的java自带的println方法,这个方法会根据当前平台的自身编码进行输出,比如你的平台环境是中文,那输出的可能就是GBK编码。

热评文章