编码与解码问题的总结

有关编码的问题在有中文的java开发中是个大问题,太多东西值得学习。自己也看了很多前辈的blog, 但是如果说要我自己总结一下这些问题, 我突然发现说不出一二三四,尤其让我感触的是,前几天看到有人问ajax乱码问题,下去写demo的时候,发现以前看的东西都忘记了,于是把我收藏的几篇关于编码的文章再次读了两遍,突然让我觉得每读一遍都有新的收获。读完了之后我觉得有必要作个总结,做个抽象,要不然又会很快忘记。再次隆重的推荐一下几篇文章:

中文化和国际化问题权威解析  ,总共7篇,非常经典,全面,建议都看。

字符,字节和编码 , 入门篇,大而化之, 也不错。

各种字符集和编码详解 

深入浅出URL编码

javascript html 相关编码问题研究

J2EE Web组件中中文及相关的问题(系列)

其实后面几篇都是在第一篇的相关文档中看到的.每篇文章都从稍微不同的角度看待编码问题.

 

字符是供人理解的,比如“中”, 无论是显示在屏幕上还是写在黑板上,我们都明白它的意思(center);但是在计算机中,为了存储这个“中”,不可能在硬盘上刻上这个字,只能存放这个字的码,因为计算机中存储和传输的基本单位是字节,所以存放的就是字节码。但是由于历史和科技发展的原因,把这个“中”编成字节码却有多个搞法,我们常见的有gb2312, gbk, utf-8.不同的编码方式对“中”进行编码得到的字节序列都不一样,比如GBK会得到[11010110,11010000],而utf-8却得到的是:[11100100,10111000,10101101];这就好比国共战争的时候,发送电文一样,电文都会被加密,否则因为他们都是中国人,一眼就看穿了。现在就以这样的场景讨论编码转换问题。

字符编码转换涉及2方面问题:字符被编码之后送出去保存或传输, 字节序列被解码之后变成字符串。假如双方战事正酣时,国民党指挥部要求发报员向前线发送了一个指挥命令“中路进军”:

可以简单的认为,String cmd = "中路进军";是以明文的方式表示字符串(实际上java是用Unicode码来表示的),也可以认为String就是一种中间状态,java上各种编码之间的转换就是依赖这个中间状态来进行的。cmd.getBytes("utf-8")就是对cmd字符串进行编码,getBytes()里面的参数就是编码方式.如果没有给定编码方式, 会默认使用iso-8859-1,.getBytes("charset")会抛出UnsupportedEncodingException,这就好比上级指示发报员用"utf-17"方式编码,可是发报员发现没有这样的译码本,于是就抛Exception. 编码之后会得到字节序列,这个字节序列在被解码之前, 失去了它原本的语义上的含义. 它就是一堆即将被保存,传输的基本单元.因为地下党遍布各处,这份加密的电文cmd.txt很快就被截获的.于是这边的谍报人员开始破译这份电文.

在涉及字符编码的时候,需要关注的无非就是两点,字符的来源, 字符的去向。但是在web开发中,这种问题被复杂化了。

用户提交请求-> 服务器接收到请求并分析参数 ->服务器根据参数找到一份文件从中读出一些内容返回给用户.这里面的各个环节如果有中文字符,都有可能出现乱码. <>

Java中的String本身不存在编码问题,一个字符串中的一个字符, 代表了ucs字符集里面收纳的一个字符,这个字符是可观存在的. 字符集也是客观世界的反映,他本身是没有所谓的编码问题,一个字符集只是代表了它能收纳多少字符.  每种字符集对对某个字符进行编号, 这个编号叫做字符编码,很显然,每种字符集的编码是不一样的,比如gb2312和ascII在容量上都不是一个数量级. 尽管每个字符集对某字符的编出不一样的代号,  但两个不同的代号都指向的是现实世界中的相同的那个字符.

字符要被传输,或者被存储, 则存的是代号, 而不会在硬盘上刻出一个字来.  既然是代号,就要问是哪种字符集的哪种编码方法产生的代号.所以, 在存储, 传出, 接收, 读取字符文件/流的时候,都要问一问, 我用的是什么编码. 如果存储和读取使用的编码不一致, 或传入/传出的编码不一致, 就会出现乱码.

1 字符文件/流保存的时候, 可以通过InputStreamWriter指定编码, 被读取的时候通过OutputStreamReader设置编码; 

3 FileReader /FileWriter会使用操作系统平台的默认编码;

2 把String流化的时候,getBytes()可以指定编码. 也可以拿一个byte数组,用new String(bytes, "charsetName");来构造字符串;

3 Java的源文件也是一个文件, 源文件被编译, 首先也是要被读出来的, 所以编译源文件的时候可以通过-encoding指定源文件的编码;

4 数据库在建库,建表的时候都可以指定编码; 读取数据库的jdbc驱动是可以在url上设置编码的;

5 jsp文件首先会被编译成servlet, jsp可以通<%@page contentType="text/html; charset=GBK"%>指定自己的编码, 注意,只是该jsp文件的编码,而不是最终输出内容的编码;

6 servlet在接收post提交的数据之前可以通过request.setCharacterEncoding()设置要接收的数据的编码,否则会使用ISO-8859-1. 在朝客户端写数据之前通过response.setContentType()设置输出编码;需要注意的是,此处设置的编码只是会在http头上产生content-type: xxx; 但并不代表最终的html输出是按照该编码输出的. 最终http内容的编码还取决于你怎么具体的输出它.

7 浏览器怎么确定文档的编码: 浏览器首先会看http头Content Type, 如果有, 就按照这个编码来读http正文,  否则再看正文的head里面的<meta http-equv="Content-Type" content="" />是否设置了编码; 如果这里也没有, 浏览器只能自己猜了. 比如浏览器可以用平台编码, 或者默认的编码;

8 用户通过浏览器POST提交表单的时候,表单内容是根据文档的content type确定的编码方式来编码;

9 url编码,get方式是在url上通过查询字符串来传递信息给后台, 所以它涉及到url编码. url编码规则: a-zA-Z0-9不需要编码;.-_*不需要编码;" "空格变加号"+".其他的字符都是不安全的,需要编码, 用什么码编码并没有规定, 但是它规定, 编码后的每一个字节用下面的方式"%XY", XY表示该字节的16进制形式.如果没有指定编码方式, 则使用平台编码, 或者由浏览器自己确定(这是很显然的);

url中的pathInfo可以和queryString使用不同的编码, pathInfo的编码由浏览器配置和server配置共同决定;

queryString的编码(比如你有a标签的一个链接上的queryString带特殊字符)由文档contentType决定;

form get方式提交的数据编码由文档contentType决定;

在浏览器地址栏上直接写中文会使用未知编码;

10 由于直接在浏览器url上写中文,各个浏览器的处理机制可能不一样从而导致编码问题,或者说,jsp/servlet的java代码执行跳转的时候带有中文,更会引发编码问题。因此用get方式如果带有中文,需要转码。java端有URLEncoder,URLDecoder进行转码,转码后的url可参考9. 需要注意的是,后端getParameter获得的值是已经被decode之后的值,不需要再次转码。javascript端有escape, encodeURI, encodeURIComponent可进行转码。但是这三个函数之间差别很大,还是有很多地方需要注意:escape不能正确处理非ASCII字符,比如中文,它总是给出%xxxx的形式,而这个形式如果放到url上,在后端是无法正确识别的;但是它可以转码%&{}这些有特殊意义的ASCII字符,escape不编码的特殊字符有@*/+;encodeURI会用把字符的utf8的16进制码变成%xx%xx形式,不编码的特殊字符更多:~!@#$&*()=:/,;?+';所有对url有特殊意义的字符都不会被编码;所以使用的时候要慎用,如果需要提交的值里面有这些字符,就不应该使用该方法;encodeURIComponent差不多和encodeURI一样,只是不编码的特殊字符非常少,只有~!*()'';它比较适合编码get提交带有特殊字符很中文的情况。

11 通过在url上encodeURI/encodeURIComponent对中文编码,在后端可以通过new String(param.getBytes("iso8859-1"), "utf-8")的方式还原中文。需要注意的是,这种方式只适合get提交,不适合post提交。怎么理解呢?比如我有param=中国被POST到后台,后台如果正确调用了request.setCharacterEncoding(),则可以得到param=中国,这个时候如果调用param.getBytes("iso8859-1"),因为"中国"都不在iso8859-1字符集里面,所以得到一个特殊的错误编码,然后你拿这个错误编码的bytes串试图通过指定utf-8来还原字符串,已经不可能了。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值