深入分析Java Web中的编码问题
编码问题一直困扰着我,每次遇到乱码或者编码问题,网上一查,问题解决了,但是实际的原理并没有搞懂,每次遇到,都是什么头疼。
决定彻彻底底的一次性解决编码问题。
1.为什么要编码
计算机的基本单元是字节,一个字节是8bit。一个字节的范围是0~255。
人类要表示的符号肯定比256个多,所以无法用一个字节来表示这个多的符号。
你想想,光汉字就有几千个。
要解决这个矛盾,有了一个新的数据结构:char。char也就是字符,最长16bit,最短为8bit。一个字符的最大长度是16bit。一个字符的范围是0~2的16次方。
所以一个char型的数据结构,可以表示这个地球上的所有字符。
但是计算机它不会为你改变啊,它的基本单元还是字节,8bit。所以如何把一个最长为16bit的char字符表示成为一个或者若干个8bit长的byte字节,
这个过程就需要编码。
2.如何翻译
各种语言交流,都需要经过翻译。一个长为16bit的char字符表示成为一个或者若干个8bit长的byte字节,
计算机中提供了很多翻译方式:ASCII,ISO-8859-1,GB2312,GBK,UTF-8,UTF-16
上面的几种编码方式都可以看成是字典,它们规定了转化的规则,按照这个规则就可以让计算机正确通过字节的来表示我们自己定义的字符char这种数据类型。
ASCII码:
ASCII码,总共128个。由于ASCII码只有128个,所以可以用一个8bit长的字节表示,也可以用一个16bit长的字符表示。
0~31表示的控制字符如换行,回车,删除等,32~126是打印字符,如大写字母,小写字母等。
ASCII码是单字节编码,也就是一个8bit长的char字符或者一个8bit的byte字符用一个字节来编码。
ISO-8859-1:
128个字符对于只使用英语的国家来说是够用了,但是对于那么西欧国家的语言来说就不够用了,因为他们国家的语言中,除了英语字母,还有其他的字符。
于是ISO组织在ASCII码基础上制定了一系列的标准来扩展ASCII码,它们是ISO-8859-1至ISO-8859-15。其中ISO-8859-1涵盖了大多数的西欧语言字符,
所以应用的最为广泛。
ISO-8859-1也是单字节编码,也就是一个8bit长的char字符用一个字节来编码。也就是扩展ASCII码之后的ISO-8859-1,总共可以表示256个字符。
GB2312:
GB2312的全称是《信息技术 中文编码字符集》,GB2312是双字节编码,也就是一个16bit长的char字符用两个字节来编码。
GB2312可以表示682个符号和6763个汉字。
GBK:
GBK的全称是《汉字内码扩展规范》,它的出现是为了扩展GB2312,以表示更多的汉字。
GBK是双字节编码,也就是一个16bit长的char字符用两个字节来编码。
GBK可以表示21003个汉字。
UTF-16:
说到UTF必须提到Unicode,ISO试图创建一个全新的超语言字典,世界上所有的语言都可以通过这个字典来相互翻译,而这就是Unicode。
UTF-16以及下面提到的UTF-8都是Unicode的不同实现形式。
UTF-16是双字节编码,也就是一个16bit长的char字符用两个字节来编码。
UTF-8:
UTF-16统一使用两个字节来表示一个字符,虽然在表示上非常简单,方便,但是也有缺点,有很多的字符用一个字节就可以表示了,但是使用两个字节来表示,造成了
存储空间的浪费。UTF-8则采用了一种变长的表示技术,每个编码区域有不同的字符长度。不同类型的字符可以由1~6个字节来表示。
UTF-8有如下的编码规则:
如果是一个字节长度的byte或者8bit长的char,最高位为0,则表示这是一个ASCII字符,UTF-8用单字节来表示。
如果一个字节以11开头,则连续的1的个数暗示这个字符的字节数。110XXXXX则表示这是char字符的第一个字节,这个char字符由两个字节组成,这个char字符16bit长,
UTF-8需要用两个字节或者更多字节来编码这个字符。
如果一个字节以10开头,表名这个字节不是char字符的第一个字节,前一个字节是这个char字符的第一个字节,也暗示这个char字符由两个字节组成,这个char字符16bit长,
UTF-8需要使用两个或者更多的字节来编码这个字符。
由上面可知一个char字符可以是8bit的,如英文字母,符号,西欧字符,也可以是16bit的,如汉字,汉语中的符号。对于8bit的char字符,除了UTF-16用两个字节来编码这个字符,
其他编码规则都是用一个字节来编码的。而对于16bit的char字符各个编码规则,则有不同字节来编码。
3.Java中涉及编码的场景:
在I/O操作中存在编码问题
我们知道在涉及编码的地方一般都在从字符到字节或者字节到字符的转换上,而涉及这种转换的场景主要是I/O。
我们站在内存的角度上,读操作是从硬盘文件上将内容传入的内存,写操作是将内容从内存传入到硬盘文件。
硬盘文件中保存的byte字节数据。(我们之所以打开文件可以看见字符,是因为文本编辑器对这些byte字节进行了解码,形成了char字符)
而内存中(此处的内存可以理解为我们的程序)的数据是char字符。也就是读操作是将硬盘文件中的byte字节转化为程序中的char字符的解码过程。
写操作就是将程序中的char字符转化为硬盘文件中byte字节的编码过程。
InputStrean,OutputStream是面向字节的输入和输出流 (父类)。这个两个流的子类用来关联存储byte字节数据的硬盘文件。
Reader,Writer是面向字符的输入和输出流 (父类)。这两个流的子类用来关联内存(程序)中的char字符数据。
而这两种流之间的编码,解码的转化由OutputStreamWriter,InputStreamReader这两个类来完成,也就是OutputStreamWriter,InputStreamReader关联了字符流和字节流,是桥梁,
这个编码和解码的过程可以指定编解码格式。如果没有指定编码,解码格式,则将使用本地环境中的默认字符集,如在中文环境中将使用GBK编码。
在Java中 一个String字符串,我们可以调用这个字符串的getBytes(编码规则),来得到这个字符串用指定编码规则编码之后的byte字节数组。
我们也可以使用 new String(byte[] byteArray ,String 编码规则),来得到一个byte字节数组,通过指定编码规则进行解码之后的字符串。
4.在Java Web中涉及的编解码
上面Java中涉及的编码是磁盘I/O,而对于Java Web而言涉及的I/O是网络I/O。通过网络I/O传输都是以字节为单位的,这就涉及到编码。
用户从浏览器端发起一个Http请求,对于这个请求,需要编码的地方有URL , Cookie ,Post表单参数。
URL的编解码:
先来区分什么是URL,什么是URI。
http://localhost:8080/examples/servlets/servlet/啦啦?name=小明
完整的URL是?前面的所有 ,也就是说name=小明这个不是URL的一部分 ,这个部分称为查询字符串
完整的URI是去掉域名http://localhost:8080之后到?之前的内容,也就是说URI是URL的一部分。
在Java中可以通过request.getRequestURL()和request.getRequestURI来分别获取一个请求的URL和URI。
我们知道请求的方式分为get和post,对于post方式提交查询字符串是通过表单的方式提交的服务器的。
对于get方式来说,虽然URL和查询字符串写在一起,浏览器却对于 它们实行了不一样的编码 ,对于URL实行了UTF-8编码,对于查询字符串实行了GBK编码。
对于post方式,对于URL也是实行UTF-8编码,由于查询字符串通过表达提交,在将对post表单编码的时候再说。
提交一个请求之后,tomcat需要对URL进行解码成字符串,才能进行匹配这个请求是请求哪个servlet。对于URL中URI的解码规则是在connector的<Connector URIEncoding="UTF-8">中指定的,
如果没有定义,那么将以默认编码ISO-8859-1来解析。那么问题就来了,如果URL中含有中文,浏览器通过UTF-8对URL进行编码,我们知道UTF-8可以编码中文使信息不丢失,但是如果tomcat没有指定<Connector URIEncoding="UTF-8">,
那么tomcat用ISO-8859-1来解码,就不行了。如果URL中没有中文,只是英文和/,我们知道UTF-8对于英文字符和特殊字符,是用一个字节来编码的,ISO-8859-1也是用一个字节来编码的,所以对于URL是英文的情况,tomcat指定解码规则是
UTF-8还是使用默认的ISO-8859-1来解码,都是可以的。所以在URL中最好不要出现中文。
对于get方式提交的查询字符串的编码格式,是在这个请求的header中的ContentType中指定的,那么在服务端使用request.getParamters("name")进行解码时(假设我们没有使用过滤器request.serCharacterEncoding()来指定解码规则),是通过
哪个规则来进行解码的?答案是要么使用默认的ISO-8859-1来解码,要么使用ContentType中的编码规则来解码,而且要使用ContentType中的编码规则来解码,需要在<Connector URIEncoding="UTF-8" useBodyEncodingForURI="true"/>
useBodyEncodingForURI="true",这个名字可能会让人混淆,不是对于整个URI使用BodyEncoding的编码规则来解码(BodyEncoding的编码就是ContentType中的编码规则),而是只对查询字符串使用BodyEncoing也就是ContentType中指定的
编码规则来解码。
可见如果使用get方式提交请求,查询字符串中出现了中文,查询字符串的编码规则在header中的ContentType中指定,而解码规则要和编码规则对应,则需要在Connector中进行设置(如果不使用request.serCharacterEncoding()来指定解码规则)。
而且如果使用request.serCharacterEncoding()指定解码规则,查询字符串中每个含有中文的参数都要单独指定解码规则,而不是像post那样,参数通过一个整体body提交,只要指定一次解码规则就行了 。
所以用get方式提交请求,查询字符串中出现中文,编解码都比较麻烦,所以我们应该尽量在通过Get方式提交的请求中,不再查询字符串中出现中文 。
Http header的编解码:
当客户端发起一个http请求时,除了上面的URL外在这个请求的header中传递的其他参数,如Cookie,redirectPath,也存在编解码的问题。
请求的header的编解码,默认都是ISO-8859-1,所以我们在添加header中参数的值时,不要在header中传递非ASCII字符。
Post表单的编解码:
post表单提交参数的方式和get方式通过查询字符串方式不同。get方式的查询字符串的编码方式是ContentType中的Charset指定的编码规则,post的方式的参数的的编码方式也是ContentType中的Charset指定的编码规则,但是不同的是我们可以
通过request.setCharacterEncoding(charset)的方式来设定post方式参数的编码规则 ,解码规则则使用ContentType中设定的编码规则。
http body的编解码:
对于响应的编解码,可以使用response.setCharacterEncoding()来设置header的ContentType的编码规则,也可直接使用response.setContentType()来设置,浏览器接到响应会按照ContentType中的编码规则去解码。
如果我们没有在响应中设置编码规则,那么浏览器会根据页面的<meta http-equiv="Content-Type" content="text/html" ;charset=GBK>中charset来解码,如果这个<meta>标签也没有设置,浏览器将会以默认的编码格式来解码。
上面所说的请求编解码,响应编解码,均是浏览器来进行处理,与页面没有关系。也就是说页面的编解码规则,与我们在这个页面上发送的请求的编码规则无关,请求的编码规则是在浏览器的设置中,每个浏览器对于请求的编码规则可能略有不同。
则向上面所说的。
5.在JS中的编码问题
外部引入JS文件
<html>
<meta charset="utf-8">
<script src="static/js/script.js" charset="gbk"></script>
这里的<script/>标签中的charset指明我们将以gbk的解码规则去解码引入的js文件。如果我们没有在这里指定<script/>中的charset。将会以这个页面<meta/>标签中的字符集规则去解码。
那么这个时候如果引入的JS文件是gbk编码,没有设置解码规则,按照页面的utf-8去解码,就有可能出现乱码的情况。
JS中的URL编码:
我们知道在页面上我们直接点击一个链接来发起请求,这个链接的编码是有浏览器的设置来进行编码的。通过JS来发起异步的请求,对于URL的编码也是默认是浏览器的设置。如果我们使用了JS框架,那么不同框架对于URL的编码也有可能不同。
实际上,我们可以主动去编码URL,一般来说URL中都是英文字母和特殊字符,无论哪种编码都是用单字节来编码这些ASCII的,那么无论用哪一种编码来解码都会解码正确。
JS中处理URL编码的函数有三个:
escape(args)
将传入的args参数,除ASCII字符之外的其他字符,按照UTF-16进行编码,并且在编码值前加上“%u”
encodeURI(args)
将传入的args参数,除除ASCII字符之外的其他字符,按照UTF-8进行编码,并且在每个编码值前加上“%”
encodeURIComponent()
比encodeURI还彻底的编码函数。
上面我们对于URL在JS中完成了编码,那么这个编码的URL,由于ASCII字符,无论哪种编码和解码都会正确,所以可以根据URL来找到正确的servlet。
而对于这个URL中的非ASCII字符的字符,我们就需要使用Java的URLDecoder类来进行解码了,URLDecoder的解码可以将“%”加utf-8编码的非ASCII字符,使用utf-8进行解码。
从而的到正确的未编码前的URL。
深入分析Java Web中的编码问题的更多相关文章
- 【中文乱码】深入分析 Java Web 中的中文编码问题
深入分析 Java Web 中的中文编码问题 1.几种常见的编码格式 1.1 为什么要编码 在计算机中存储信息的最小单元是 1 个字节,即 8 个 bit, 所以能表示的字符范围是 0 ~ 255 个 ...
- JAVA WEB 中的编码分析
JAVA WEB 中的编码分析 */--> pre.src {background-color: #292b2e; color: #b2b2b2;} pre.src {background-co ...
- Java web中常见编码乱码问题(二)
根据上篇记录Java web中常见编码乱码问题(一), 接着记录乱码案例: 案例分析: 2.输出流写入内容或者输入流读取内容时乱码(内容中有中文) 原因分析: a. 如果是按字节写入或读取时乱码, ...
- Java web中常见编码乱码问题(一)
最近在看Java web中中文编码问题,特此记录下. 本文将会介绍常见编码方式和Java web中遇到中文乱码问题的常见解决方法: 一.常见编码方式: 1.ASCII 码 众所周知,这是最简单的编码. ...
- 深入分析Java Web中的中文编码问题
要对Java Web项目进行编码原因: 1.在计算机中存储信息的最小单位是1个字节,即8个bit,所以能表示的字符范围是0~255个. 2.电脑需要表示的符号太多.无法用1个字节完全表示. 要解决这个 ...
- 第三章 深入分析Java Web中的中文编码问题
3.1 几种常见的编码格式 3.1.1 为什么要编码 一个字节 byte只能表示0~255个符号,要表示更多的字符,需要编码. 3.1.2 如何翻译 ASCII码:有128个,用一个字节的低7位表示. ...
- Java Web中的编码解析
在springmvc工程web.xml中配置中文编码 <!-- 配置请求过滤器,编码格式设为UTF-8,避免中文乱码--> <filter> <filter-name&g ...
- 深入分析Java Web技术内幕(修订版)
阿里巴巴集团技术丛书 深入分析Java Web技术内幕(修订版)(阿里巴巴集团技术丛书.技术大牛范禹.玉伯.毕玄联合力荐!大型互联网公司开发应用实践!) 许令波 著 ISBN 978-7-121- ...
- 《深入分析Java Web技术内幕》读书笔记 - 第1章 深入Web请求过程
第1章 深入Web请求过程 1 1.1 B/S网络架构概述 2 基于统一的应用层协议HTTP来交互数据. 1.2 如何发起一个请求 4 HTTP连接本质是建立Socket连接.请求实现方式:工具包如H ...
随机推荐
- Luogu Dynamic Ranking (带修改的主席树)
题目大意: 网址:https://www.luogu.org/problemnew/show/2617 给定一个序列a[1].a[2].....a[N],完成M个操作,操作有两种: [1]Q i j ...
- python迭代和切片
from collections import Iterable #切片************************ # #取一个list或tuple的部分元素是非常常见的操作 ,Python提供 ...
- wpf 研究之道 winform or wpf,u choose who?
很久以前,我们用winform做过一个五子棋的程序,当时用winform的画图,先画出棋盘...后来项目的研究阶段,偶尔用winform做个小工具.闲暇之余,看到介绍wpf的资料,只知道它采用了xam ...
- nodejs辅助前台开发系列(1) 搭建简单HTML开发环境
搭建简单的html开发环境一般需要解决两个问题: 文本编辑器 WebServer集成 在文本编辑器选择上,VS Code 无疑是一匹黑马,谁用谁知道.WebServer集成nodejs对前端来说最为友 ...
- ubuntu16.04 安装常见问题解决方案------输入法黑框
我的系统是 lubuntu 16.04 刚安装输入法候选字的地方全是黑框,然后百度查到了 compton 和 xcompmgr 这两个说是窗口微调 透明 ,这两个方法对我的系统不管用 .各位如果遇到黑 ...
- 属性动画 ValueAnimator 运行原理全解析
最近下班时间都用来健身还有看书了,博客被晾了一段时间了,原谅我~~~~ 提问环节 好,废话不多说,之前我们已经分析过 View 动画 Animation 运行原理解析,那么这次就来学习下属性动画的运行 ...
- JavaScript编码规范(1)
参考的是百度公司的JS规范,分为两部分.这是第一部分 [建议] JavaScript 文件使用无 BOM 的 UTF-8 编码. 空格 [强制] 二元运算符两侧必须有一个空格,一元运算符与操作对象之间 ...
- 在Vue.js2.0中组件模板子元素数量问题
在Vue中当利用组件进行开发时候,组件所使用的模板只可以应用于一个根实例,当你需要添加多个子元素的时候,可以用一个div将它们包裹起来,代码如下: <template id="task ...
- 让wordpress标签云显示文章数的正确方法
先看一下效果 在百度经验找到一个教程,可惜,根据实践发现方法是错误的, 百度经验上的代码: 1 2 3 4 5 6 7 8 9 10 11 12 //标签tag所包含的文章数量 function Ta ...
- iOS加密算法总结
常用加密算法: DES:Data Encryption Standard,即数据加密算法,它是IBM公司于1975年研究成功并公开发表的. DES(数据加密标准)原理: DES是一个分组加密算法,它以 ...