编码的本质
编码将内存字节作用于磁盘文件或者网络文件的过程,是将磁盘文件/网络文件 反解析成内存字节的过程.
这个过程中如果 内存字符串到 “字节数组”的编码 与 网络/磁盘文件的之间转化的编解码方式不一致或者不兼容就 会产生乱码.
写文件的正常流程:内存字符串->“使用编码转化为(1)”-> 字节数组 ,writer将字节数组写入文件,文件头中的编码方式如果和 字符串转化为字节数组的编码不一致或者不兼容,打开文件/再次使用文件头中的编码方式读取文件的时候就会显示乱码, 这个时候方式处理是: 如果明确知道(1)中使用的编码,那么打开文件或者读取文件主动指定(1)中使用的编码。
上面讲了乱码的原理性,在判断乱码的时候需要考虑输入源的编码是什么,输出源使用了什么编码 , 下面看几个乱码的例子.
乱码的几个例子及思考解决方案
浏览器输入到后端乱码
现象不再赘述
理论上浏览器发送请求的时候,指定编码 并使用指定的编码编码request param或者body 那么后端只要使用浏览器说的编码来 解码就能得到正确内容.
排查方式为 确认request header中的编码方式 与后端接收到并且解码的编码方式是否一致,可以使用抓包的方式判断。如果request header中没有指定编码方式,那么就看浏览器以及服务端的默认编码方式了, 所以在协议交互的过程中编码方式是最好显示指定
后端返回浏览器乱码
现象不再赘述
理论上后端返回response的时候会在response header中带上编码方式,并使用指定的编码方式编码response body 那么浏览器只要使用response header中的编码来 解码就能得到正确内容.
排查方式为 确认response header中的编码方式,了解不同浏览器的默认编码逻辑. 以及html中的其他元素标签对编码的支持
java 枚举类乱码
public enum OfcDeliveryStatusEnum {
init(0, "数据库-初始化"),
create(1, "生成运单"),
accept(2, "配送待分配")
private Integer code;
private String desc;
}
现象:当在代码中想要使用OfcDeliveryStatusEnum.create.getDesc()的时候 发现获取到的是乱码
原因:java源文件是UTF-8格式保存的,maven编译使用的GBK格式编译生产class文件,这中间有一个文件读取到内存再输出到文件的过程,编码的差异导致格式被破坏从而产生乱码.
排查方式需要明白java的工作原理,OfcDeliveryStatusEnum.create.getDesc() 程序运行起来后直接从内存的metaspace中取出来的,如果内存中通过utf-8查看是乱码,那么内存中已经乱码了,而内存值是通过加载class文件生成的,那么class文件难道也是乱码吗?class文件是maven从java源文件编译而来,源文件正常,那只可能是编译过程出了问题
数据库存储乱码
现象不再赘述
应用程序连接mysql server之间有一次通信会话写入数据,mysql server到 存储到磁盘有一次文件存储,从mysqlserver 查询数据返回到前端有一次通信会话 可以看到这中间有3次通信,那就存在三次编码转化,分别抓住两次编码转化是否一致。
1)character_set_server:mysql server默认字符集。
2)character_set_database:数据库默认字符集。
3)character_set_client:MySQL server假定客户端发送的查询使用的字符集。
4)character_set_connection:MySQL Server接收客户端发布的查询请求后,将其转换为character_set_connection变量指定的字符集。
5)character_set_results:mysql server把结果集和错误信息转换为character_set_results指定的字符集,并发送给客户端。
6)character_set_system:系统元数据(字段名等)字符集
Linux系统显示乱码
https://help.aliyun.com/document_detail/169436.html
总结
不管什么场景,只要存在一次读写操作,如果使用的编码不一致就可能导致乱码, 现实中碰到的场景都可能使用多次读写操作的复合场景,判断乱码的方式需要分段判断,最终确认第一次出现编码不一致的一段