编码/乱码背后理论、常见场景应用(容器、tomcat、iso-8859-1、unicode、utf-8等)

6 篇文章 0 订阅
1 篇文章 0 订阅

又涉及到web工程编码问题,在此将关联点梳理一遍。

0、编码常见问题在于前后端编码和其他接口调用编码的场景,这类场景乱码的避免,最直接有效的方法还是靠encode/decode。
但是本文不限于这方面应用的讨论,而是从编码理论、容器背景角度进行说明。

1、首先从浏览器角度说明编码信息正确性:

(1)F12的network流拦截器中看到的General部分的信息(特别是Request Url)是准确的,是真正的请求,具备“所见即所得”的编码信息;
(2)浏览器的url输入框中的信息,的确会被浏览器encode一次,但是只有判断存在“需要encode”的情况下(比如汉字),而且,当请求获得响应,url输入框中信息会根据“若存在decode一次就能更【明显】”的准则变化(比如汉字),注意此处自动decode只是为了“显示更显眼”,不代表真正的编码信息,与前边encode不同,比如“参数含有汉字”,底层实际会encode一次(可通过(1)查看),而如果“参数是汉字的首次encode码”,则底层不会encode(可通过(1)查看),但是响应后会“变为汉字”;
(3)F12点network流拦截器中看到的“Query String Parameters”是假象,如果是页面自发请求,则参数是多少(或者(1)中General中参数是多少),此处就如何显示,而如果是通过url输入框发出请求,此处是对参数点Decode处理,是为了“显示更显眼”,而且与(2)中“变为汉字”不同的是,连二次encode值也会被decode一次((2)中二次及以上encode值不会变)。

2、根据【1】中描述,足以判断“真实的编码的网络信息”,据此讨论服务器端编码:

(1)容器(包括tomcat、Jboss等),均会对接收到的信息进行“试探性的decode”(如果信息格式可以decode则decode,否则不处理),且此步骤发生的时间点非常早(先于容器中其他filter,可通过tomcat源码深入研究),暂且称之为“解码”;(2)“解码”涉及编码格式,该格式在容器点xml配置文件中配置,一般是web.xml,tomcat7默认是iso-8859-1,tomcat8默认是utf-8,源码参考链接【https://blog.csdn.net/Voteless_Cz/article/details/76832854】;
(3)“解码”后会发生“转码”,会发生“以容器默认编码当作解码后信息的原始编码,转变为容器中应用程序所需要的编码格式”;
(4)一般而言,前端页面会设定页面编码为utf-8,也就是请求参数的基本编码格式,此时如果是页面自动发出请求,且其参数encode一次,则服务器会自动执行“decode(param, charset)”,如果服务器不是utf-8则乱码;如果页面没有encode,则服务器会发生(3)的流程,以默认编码当作数据原始编码,转为应用程序所需编码,比如java就是utf-8(好像是);如果页面encode两次,或多次,则解码正常变为“一串数字字母”,转码也就不存在问题了,后续应用程序可以对此正常处理。

3、再梳理下“编码”的本质:

(1)数字信息底层都是“0、1”,“编码”本质是一种将“0、1”映射到字符串“抽象意义”的映射(这映射的两头是最重要的),而特定的字符串“我、abc、123”是对“抽象意义”等“图像化、具象化”的显示,都是我们在屏幕看到的表象,这些信息相比前两者并不重要,核心仍是“抽象意义”的映射;
(2)举例,基于utf-8的信息,本质是一长串“0、1”组合(虽然是0、1,但是其基于所在页面等载体的编码设定可对应相应的字符图像,可具象化),“0、1”组合经过了网络传输,并不改变。容器对数据的处理看似一步,实际两步,首先容器接收时并不知道这套“0、1”组合基于什么编码映射,会用自己的默认编码来解释之,当然解释的过程中无法记录抽象意义,进而涉及第二步,抽象意义转为当前存储所需要的记录格式,亦即“以当前存储所规定的编码格式将抽象意义转化为新的‘0、1’组合”,据此才能在后续实现对人类而言有意义的“图像化”。乱码出现的原因:“0、1”组合数据流进入容器后(假设源数据是utf-8编码),容器如果以iso-8859-1解码,则解析的抽象意义就错了,比如 0000 0001本来是utf-8的‘我’,在iso-8859-1对应‘你’,就错了,更甚者在后者编码集找不到对应抽象意义,则空或‘?’或其他,进而,转为容器内部应用所用编码格式(假设是utf-8),则抽象的‘你’对应utf-8中的‘你’的‘0、1’组合(显示时也可对应找到‘你’的文字图像,虽然不显示汉字‘我’),而空或‘?’或其他则映射到utf-8相应的‘0、1’组合,但是显示会乱码。

4、web工程其他编码场景:

(1)http的响应信息中,header有charset设置,但只是对body体点编码设置,而header自身是默认“iso-8859-1”的。这也是为何很多下载功能需要将“fileName”从系统等utf-8等格式转为iso-8859-1的格式。
(2)xml http返回的数据,默认是utf-8;get(post?)默认是utf-8。
(3)web工程代码中的string类型,其存储的本质还是“0、1”组合,我们对string的操作,本质上都是对“0、1”组合的操作,只不过:a、java会基于utf-8的默认编码(可以设定,否则jvm依据系统编码)对操作进行处理,亦即在底层中存在“操作字符-->操作字节”的转换,研发者不需要涉及这个转换;b、当调用system.out.print时,实际上是java将“0、1”组合映射到对应文字“图片”进行映射后再对其“打印输出”而已。因此,理解了上述简单方法的底层本质,也就可以理解“string fileName_2=new string(fileName_1.getBytes("utf-8"), "iso8859-1")",对文件名获取bytes后,研发者可以对字节进行处理,不会有“java在底层进行转换或映射”的过程,再以新编码iso8859-1转化后,就成了新的str也就是新的“0、1”组合,但是iso8859-1的编码而非默认utf-8的编码,注意:此时,新编码只是一个业务编码,系统编码仍然是utf-8,如果研发者调用字符处理方法或system.out.print等方法,也依然基于utf-8进行转换或映射,结果会打印乱码——另外,此时的fileName_2被set给其他方法、或被return时,本质是“0、1”组合字节码的赋值或返回,这也就解释了“前述fileName_2可以被用于response的setHeader方法中来解决文件下载时文件名乱码”的问题。
【20210527】
对“0、1”组合字节码与编码的关系举例可以查看【https://blog.csdn.net/x_iya/article/details/54133247】,但是文中有几句不够准确“这样得到的byte[]数组才能正确被还原”,这里不存在“字节码的还原”,而仅仅是“字节码在正确编码环境下的映射与展示”。字节码才是字符串的本质,而如果仅仅止步于string字符串本身,很多数据流场景、网络数据场景的逻辑就无法用统一的思维逻辑来完成逻辑闭环。
【/20210527】

【20210629】
5、java编码

java默认编码随系统,比如mac、linux一般都是utf-8,也可以在ide进行设定“project encoding”改变之;但无论如何,java工程中,乃至内存中的“0、1编码”是基于该设定编码。
不再详述java中“码点与代码单元”的区别,只需知道码点是业务意义、抽象意义的单个字符,而代码单元则是实际的编码单元,可以认为是一个双字节的char(char当然是双字节)。可以通过codePoint相关方法获取相关结果。
回到编码问题,可以通过getbytes方法获得“从双字节char的代码单元到单字节的byte(数组)”的转化,如果希望打印出来,最好对byte单元使用integer的toBinaryString方法获取(方法参数int类型,会将byte先补足32位,前边/左边位数类似>>用高位符号位补足,而不是>>>那样都用0补足,因此要与“0xff进行与运算”得到前边三个字节都是0的结果,这样打印时就不会有太多无用的1;当然,如最后的8位的前几位还有0,也会被省略打印);另一个打印方法是对byte中每个“4位”进行转化为“0、1、2、……、e、f”的字符串,更直观,就是麻烦点得自己编。这里注意,getbytes转化为unicode、utf-16时(或本身默认编码就是utf-16时直接用getBytes),字符串前边都会加一个双字节feff。
注意,java中所有\u开头的字符串,都认为是unicode字符串(虽然与utf-16编码一致,但这里是手写数据,后者是内存编码),会在java编译前先解析为字符,进而再做编译,参考jsp中的xss漏洞原理。
另外,很多情况下,byte作为内存中的字节数据在很多情况下会被转化为int(比如作为“参数为int类型的方法”的参数时),该过程与前述>>类似 会发生高位补足的情况,然后在使用时(比如转化结果被打印时)会作为一个int被处理,而且是一个“作为原生补码的0、1组合”的int被处理。例如,unicode默认的前边feff的f补足后成为-1(补码为32个1,后8个1位原生,前24个则为“根据后8个1的第一个1而进行的补足)。
【/20210629】

【20210528】
6、其他

(1)一份编码的参考说明:https://blog.csdn.net/shootyou/article/details/45672091
【/20210528】
【20210721】
(2)mysql中utf-8最多只能存储“三个字节”的特殊字符,因此实际应用中要尽量使用utf8mb4。
【/20210721】

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值