详解服务器和浏览器之间数据的编码转换

 web开发中遇到最头痛的问题莫过于乱码的问题,乱码的问题看似简单,其实是很复杂的,涉及的知识面太广,操作系统的编码、文件编码、服务器返回的数据所使用的编码、服务器告诉浏览器返回数据所使用的编码、浏览器展示页面时所使用的编码等等,要真正明白各种乱码的原因,你甚至还要理解各种编码的编码原理,像UTF-8UTF-16GBK等。下面由一个简单的程序来引出主题的探讨:

    

  

@Controller

@RequestMapping("/test") 

public class TestController{

 

  @RequestMapping("sayhi")

   public void sayHi(HttpServletRequest req,HttpServletResponse res){

     res.setCharacterEncoding("UTF-8");

     res.getWriter.write("世界,我来了");

   }

}

 

   上面是spring mvc一个简单的Controller向客户端输出一段中文字符串,下面是浏览器(firefox4.0.1)的输出

  

    
详解服务器和浏览器之间数据的编码转换_第1张图片
 

 

从服务器返回的数据长度可知道,数据的确是按照UTF-8编码的,但响应头并没有编码信息,即浏览器并不知道服务器返回的数据编码方式,由Accept-Charset知,浏览器展示页面用的UTF-8编码,那么浏览器怎么讲服务器的数据展示出来呢?

浏览器要展示必须将服务器端的数据转换为UTF-8编码,但浏览器并不知道服务器数据的编码方式如何转换呢?当浏览器不知道服务器返回数据的编码方式时,就把它的编码方式当做和操作系统的字符集编码一样。因为我使用的是中文操作系统,所以浏览器展示页面时,就将服务器返回的数据以gb2312的方式转换为UTF-8的编码。但上图中服务器返回的数据实际是UTF-8编码的,有9个字节,被当做GB2312来处理当然会有问题。

 

上面的根本问题就在于浏览器把服务器UTF-8编码的数据当做GB2312编码来处理,那如何让浏览器知道服务器返回数据的编码方式呢?很简单,在响应头中设置编码方式就行了,像下面这样:

  

res.setContentType("text/plain;charset=UTF-16")

res.getWriter.write("世界,我来了")

 

res.setCharacterEncoding("UTF-16")

res.setContentType("text/plain")

res.getWriter.write("世界,我来了")

  

浏览器的输出:

 


详解服务器和浏览器之间数据的编码转换_第2张图片
  

你可能会觉得奇怪,为什么服务器的数据编码是UTF-16(content-type中指定的,数据长度也是14),浏览器展示页面用的UTF-8的编码(accept-charset中指定的),而没有出现乱码?这是因为浏览器知道服务器返回数据的编码方式是UTF-16后,它会就数据从UTF-16的方式转换为UTF-8编码,数据就能正确展示出来。所以charset中不管你指定什么编码(前提是这种编码支持中文,像ISO8859就不支持中文。如果指定为ISO8859编码,就会有数据丢失,就不能转换accept-charset指定的编码),浏览器都能正确展示。

 

到这里,你应该似乎明白了数据在服务器和浏览器之间的编码转换过程。那我们再看下面的例子:

 

 res.setCharacterEncoding("UTF-16")

 res.getWriter.write("世界,我来了")

 浏览器输出:

 
详解服务器和浏览器之间数据的编码转换_第3张图片
 上面这幅图除了响应头的数据长度(由数据长度可知服务器端数据的编码方式确实是UTF-16),响应和请求头其他的信息和第一幅图一样,那浏览器为什么能正确显示数据呢?这是因为由于没有指定服务器端返回数据的编码方式(即conent-type中没有指定charset),浏览器就把服务器返回的数据当做gb2312编码来转换为UTF-8编码来展示出来,而原来是UTF-16编码的数据当做gb2312来处理时没有问题的,具体原因大家可以去看gb2312和UTF-18的编码原理,所以浏览器能正确显示数据。

 

 这案例中有以下几点要注意:

      1、若只调用setCharacterEncoding(),则只是高告诉服务器返回的数据的编码方式,并没有告诉浏览器数据的编码方式(即不会产生content-type头字段)。

     

      2、只有在调用setContentType()时指定具体的mime-type才会产生conent-type头字段,若没有调用setCharacterEncoding(),也没有指定charset,则会使用charset则会被设置为ISO8859-1;调用了setCharacterEncoding(),则使用该方法指定的字符集

     

      3、setContentType()中设置的charset会覆盖setCharacterEncoding()方法。

 

    

博客新地址

你可能感兴趣的:(java,编码,乱码)