javaweb/mysql数据库乱码/io流乱码的原因与解决方法
1.编码的产生:我们的存储的基本单元是一个byte字节,然而人类的语言太多,无法用一个基本单元来表示,然而为了拆分或者一些相应的翻译工作,导致了编码的产生
2.常见的编码格式
ASCll :用一个字节的低7位共128个来表示,0-31表示控制字符,32-126表示打印字符, ASCll的二进制第一位也就是最高位,目的是用来做奇偶检验的,所谓奇偶校验,是指在代码传送过程中用来检验是否出现错误的一种方法,一般分奇校验和偶校验两种。奇校验规定:正确的代码一个字节中1的个数必须是奇数,若非奇数,则在最高位添1;偶校验规定:正确的代码一个字节中1的个数必须是偶数,若非偶数,则在最高位添1;看到这是似乎对奇偶检验还是较为模糊,不大明白,例如字母A的ASCII码为1000001,若使用偶校验的结果是:01000001,奇检验的结果是11000001;奇偶检验是事先设定好了的,当CPU读取存储的数据时,它会再次把前8位中存储的数据相加,计算结果是否与校验位相一致。从而一定程度上能检测出内存错误,奇偶校验只能检测出错误而无法对其进行修正,同时虽然双位同时发生错误的概率相当低,但奇偶校验却无法检测出双位错,检验到错误的时候,接收数据方就会要求发送方重新传数据
范例:
串行数据在传输过程中,由于干扰,可能使位变为1,(为什么不变0?脉冲)这种情况,称为出现了"误码"。传输中的错误,叫"检错"。发现错误后消除错误叫"纠错"。最简单的检错方法是"奇偶校验",一般在同步传输方式中常采用奇校验,而在异步传输方式中常采用偶校验
ISO-8859-1:由于128字符不够用,于是ISO组织在ASCll的基础上制定一系列的标准来拓展ASCll编码(ISO-8859-1~ISO-8859-15),ISO-8859-1也是作为java web中默认的解析方式
GB2312:全称是《信息交换用汉字编码字符奇基本集》,占两个字节,其中A1~A9是符号区,B0~F7是汉字区
GBK:全称《汉字内码拓展规范》,是对GB2312的拓展,它的编码是与GB2312相兼容的,也就是说用GB2312编码的汉字可以用GBK来编码而不会乱码
GB18030:我国强制标准,可能是单字节 双字节 四个字节,与GB2312编码是兼容的,我国标准,但是使用并不广泛
UTF-16:定义了Unicode(世界上所有的语言都可以用这本字典来相互翻译,是java和xml的基础)在计算机中的存取方法,用两字节来表示Unicode的转换格式,在表示上相对来说简单方便,但是相对于UTF-8来内存空间增大了很多
UTF-8:对每个编码区域有不同的字码长度,不同类型字符可以有1-6个字节组成
UTF-8的编码原则:
如果是一个字节,最高位为0,
如果一个字节以11开头,表示相应的首字节
如果以10开始,则表示他不是首字节,需要向前查找才能得到当前字符的首字符
例如:
1字节 0xxxxxxx
2字节 110xxxxx 10xxxxxx
3字节 1110xxxx 10xxxxxx 10xxxxxx
4字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
5字节 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
6字节 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
说明:UTF-8用来表示字符编码的实际位数最多有31位,即上表中x所表示的位。除去那些控制位(每字节开头的10等),这些x表示的位与UNICODE编码是一一对应的,位高低顺序也相同。 将UNICODE转换为UTF-8编码时应先去除高位0,然后根据所剩编码的位数决定所需最小的UTF-8编码位数(ASCll标准编码为7位)。
3.本地io流的编解码
1)乱码的原因
文件的传输时使用二进制数传输(备注:字符的显示是经过相应的转换之后得到的结果),在转换成二进制的过程中具有编码和解码两个过程,编码与解码一致那么就不会乱码,假如不一致就会乱码。例如传入的文件java默认使用的是gbk解码,而我们原本使用的是utf8编码,那么在解码的时候会相应的乱码。
2)解决方法
a)在知道原文件的编码的情况下
InputStreamReader isr = new InputStreamReader(new FileInputStream(""),"utf-8");
b)在不知道编码的情况下获取本地文件的编码方法如下
//简写EncodingDetect的使用方法
String pathname = "F:/test/file1/file2.txt"; //获取编码格式 String encode = EncodingDetect.getJavaEncode(pathname);
System.out.println(encode);
EncodingDetect.readFile(pathname, encode);
c)在不知道编码的情况下获取网络文件的编码方法如下
public static String getFileEncode(String path) { /* * detector是探测器,它把探测任务交给具体的探测实现类的实例完成。 * cpDetector内置了一些常用的探测实现类,这些探测实现类的实例可以通过add方法 加进来,如ParsingDetector、 * JChardetFacade、ASCIIDetector、UnicodeDetector。 * detector按照“谁最先返回非空的探测结果,就以该结果为准”的原则返回探测到的 * 字符集编码。使用需要用到三个第三方JAR包:antlr.jar、chardet.jar和cpdetector.jar * cpDetector是基于统计学原理的,不保证完全正确。 */ CodepageDetectorProxy detector = CodepageDetectorProxy.getInstance(); /* * ParsingDetector可用于检查HTML、XML等文件或字符流的编码,构造方法中的参数用于 * 指示是否显示探测过程的详细信息,为false不显示。 */ detector.add(new ParsingDetector(false)); /* * JChardetFacade封装了由Mozilla组织提供的JChardet,它可以完成大多数文件的编码 * 测定。所以,一般有了这个探测器就可满足大多数项目的要求,如果你还不放心,可以 * 再多加几个探测器,比如下面的ASCIIDetector、UnicodeDetector等。 */ detector.add(JChardetFacade.getInstance());// 用到antlr.jar、chardet.jar // ASCIIDetector用于ASCII编码测定 // detector.add(ASCIIDetector.getInstance()); // UnicodeDetector用于Unicode家族编码的测定 // detector.add(UnicodeDetector.getInstance()); java.nio.charset.Charset charset = null; URL url=null; try { url = new URL(path); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { charset = detector.detectCodepage(url); } catch (Exception ex) { ex.printStackTrace(); } if (charset != null) return charset.name(); else return null; };
备注:本地文件可以使用EncodingDetect jar包来获取文件的编码,利用统计学得出的结论,并不是百分百正确,但是准确率在百分之99以上,网络爬虫可以使用cpdetector_1.0.10.jar*,基于统计学。
4.mysql数据库乱码
解决方法:如下
useUnicode=true&characterEncoding=utf8&characterSetResults=utf8 //需要与数据的编码集一致,否则可能插入出错(例如数据库是latin1,而我们设置的utf-8),数据库的字符集可以设置也可以查看。(mysql数据库中)
含义如下:本质:useUnicode=true&characterEncoding=utf- 8的作用是指定character_set_client和character_set_connection的值,characterSetResults=utf8对应character_set_results
5.web乱码
1)post请求方式乱码
原因:默认使用的iso-8859-1解析,导致乱码//客户端需要与服务器端保持编解码一致
解决方法:采用request.setcharacter(“utf-8”)(request.setCharacterEncoding()是在接受数据之后进行相应的解码),或者是response.setcharacter(“utf-8”),也可是以response.setContentType("text/json; charset=UTF-8")来解决,可以使用拦截器拦截设置编码;
a)基于配置的springframework内置拦截器如下,需要放在所有的拦截的最前面,防止在正确解码前以及被解码导致乱码
<filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> <init-param> <param-name>forceRequestEncoding</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>forceResponseEncoding</param-name> <param-value>true</param-value> </init-param> </filter>
b)在spring中不基于配置的方式配置拦截器如下
在类路径下建立meta-inf文件夹,然后新建services,然后建立一个文件(javax.servlet.ServletContainerInitializer),在文件中写下全类名(cn.test.config.MainConfig)
package cn.test.config; import org.springframework.web.filter.CharacterEncodingFilter; import javax.servlet.*; import javax.servlet.annotation.HandlesTypes; import java.util.EnumSet; import java.util.Set; public class MainConfig implements ServletContainerInitializer { @Override public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException { // characterEncodingFilter添加拦截器 FilterRegistration.Dynamic characterEncodingFilter = ctx.addFilter("characterEncodingFilter", CharacterEncodingFilter.class); characterEncodingFilter.setInitParameter("encoding","utf-8"); characterEncodingFilter.setInitParameter("forceRequestEncoding", "true"); characterEncodingFilter.setInitParameter("forceResponseEncoding","true"); characterEncodingFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST),true,"/*"); } }
2)get请求方式乱码(post的解决方法对get无效)
原因:对于get方法表单提交的内容在URL解析开始时就已经解码,所以setCharacterEncoding()无效。
解决方法:
在Tomcat的server.xml中的:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="GBK" />