一篇文章帮你解决中文乱码问题---JavaWeb中文编码问题全面解析

需要编码的原因

  • 计算机中存储的最小单元是一个字节,即8bit,所以能表示的字符范围是0~255个。
  • 人类要表示的符号太多,无法用一个字节来完全表示。
  • 要解决这个矛盾必须要有一个新的数据结构char,从char到byte必须编码。
  • 编码格式一般分为如下几种:
  1. ASCII
  • ASCII码共有128个,用一个字节的低7位表示,0~31是控制字符,如换行、回车、删除等;32~126是打印字符,可以通过键盘输入并能够显示出来。
  • ISO-8859-1
    • 128个字符显然是不够用的,ISO组织在ASCII码的基础上又制定了一系列标准用来扩展ASCII编码,他们是ISO-8859-1~ISO-8859-15,其中ISO-8859-1涵盖了大多数西欧语言字符,所以应用最广泛。ISO-8859-1任然是单字节编码,它总共能表示256个字符。
  • GB2312
    • 它的全称是《信息交换用汉字编码字符集基本集》,它是双字节编码,总的编码范围是A1~F7,其中从A1~A9是符号区,总共包含682个字符。从B0~F7是汉字区,包含6763个汉字。
  • GBK
    • 全称《汉字内码扩展规范》,为了扩展GB2312加入了更多的汉字,它的编码是和GB2312是兼容的,也就是说GB2312编码的汉字可以用GBK来解码,并且不会有乱码。
  • GB18030
    • 是我国强制标准,它可能是单字节、双字节、或者四字节编码,与GB2312兼容,应用并不广泛。
  • UTF-16
    • 用两个字节来表示Unicode转化格式,它是定长的表示方法,不论什么字符都可以用两个字节表示,两个字节是16bit,所以叫UTF-16。每两个字节表示一个字符,这就大大简化了字符串的操作,这也是java以UTF-16作为内存字符存储格式的一个很重要的原因。
  • UTF-8
    • UTF-16同意采用两个字符表示一个字节,但是很大一部分字符用一个字节就可以表示现在却要用两个字符表示,存储空间放大了一倍,而现在网络带宽还非常有限,这样会增大网络传输的流量,而且也没必要。而UTF-8采用了一种变长的技术,每个编码区域有不同的字码长度。不同类型的字符可以由1~6个字节组成。

java中需要编码的场景

I/O操作中存在的编码
  • 涉及编码的地方一般在字符到字节或者字节带字符的转换上,二需要这种转换的场景主要是I/O。
  • Reader类是java的i/o中读字符的父类,而inputstream类是读字节的父类,inputstreamreader类就是关联字节到字符的桥梁,它负责在I/O过程中处理读取字节到字符的转换,而具体字节到字符的解码又委托streamdecoder去做,在streamdecoder解码过程中必须有用户指定charset编码格式,如果没有指定charset,将使用本地环境中的默认字符集。

  • 写的情况也类似,字符的父类是writer,字节的父类是outputstream,通过outputstreamwriter转换字符到字节。StreamEncoder类负责将字符编码成字节,编码格式和默认编码规则与解码是一致的。
  • 强烈建议不要使用操作系统的默认编码,因为这样你的应用程序的编码格式就和运行环境绑定起来了,在跨环境是很可能出现乱码。

内存操作中的乱码
  • 内存中进行字符到字节的转换也很常见。

String s = "这是一段中文字符";byte[] b = s.getBytes("utf-8");String n = new String(b,"utf-8");Charset charset = Charset.forName("utf-8");ByteBuffer byteBuffer = charset.encode(string);CharBuffer charBuffer = charset.decode(byteBuffer);

Java中如何编解码
  • 以字符串“I am 君山”为例。
  • 按照ISO-8859-1编码

  • ISO-8859-1是单字节编码,中文“君山”被转化成值是3f的byte,3f也就是“?”字符。所以经常会出现中文变成“?”,很可能就是错误地使用了ISO-8859-1编码导致的。
  • 按照GB2312编码
  • GB2312字符集有一个char到byte的码表,不同的字符编码就查这个码表找到与每个字符的对应字节,然后拼装成byte数组。
  • 按照GBK编码
    • GBK与GB2312编码结果是一样的;
    • 由此可以看出来GBK编码是兼容GB2312编码的,他们的编码算法是一样的。
    • 不同的是它们的码表长度不一样,GBK包含的汉字字符更多,所以只要是经过GB2312编码的汉字都可以用GBK进行解码,反之则不然。
  • 按照utf-16编码
  • 用utf-16编码将char数组放大了一倍,单字节范围内的字符在高位补0变成两个字节,中文字符也变两个字节。
  • 编码效率非常高,规则很简单。
  • 按照utf-8编码
  • UTF-16采用顺序编码,不能对单个字符的编码值进行校验,如果中间的一个字符码值损坏,后面所有的码值都将受到影响。
  • 而UTF-8不存在这些问题,UTF-8对单字节范围内字符任然用一个字节表示,对汉字采用三个字节表示。
  • UTF-8编码与GBK和GB2312不同,不用查码表,所以在编码效率上UTF-8的效率会更好,所以在存储中文字符时UTF-8编码比较理想。
  • 几种编码格式的比较
    • GB2312与GBK的编码规则类似,但是GBK范围更大,所以GB2312与GBK比较,应该选择GBK;
    • utf-16与utf-8都是处理Unicode编码,编码规则不太相同,相对来说utf-16编码效率最高,字符到字节相互转换更简单,进行字符操作也更好,它适合本地磁盘和内存之间使用,可以进行字符和字节中间的快速切换,java内存编码就采用utf-16编码;
    • 但是UTF-16不适合网络之间的传输,因为网络传输容易损坏字节流,一旦字节流损坏就很难恢复,相比较而言,utf-8更适合网络传输,单个字符的损坏不会影响后面其他字符,编码效率介于GBK和UTF-16之间;
    • UTF-8在编码效率上和安全性上做了平衡,是理想的中文编码方式。

Java Web中涉及的编解码
  • URL的编解码
    • 浏览器编码URL将非ASCII字符按照某种编码格式编码成16进制数字后在每个16进制表示的字节前加上“%”,所以就出现了如下情况:

http://tanqingbo.com/2016/05/11/%E5%A4%8F%E4%BB%A4%E8%90%A5%E6%B1%87%E6%80%BB/

这就是为什么我们在浏览器的地址栏中能看到中文,但是把地址拷贝出来后中文就变成了一些奇怪的串了。

  • http Header的编码
    • header中传递参数,如:Cookie、redirectPath等,这些用户设置的值可能存在编码问题。
    • 对header进行解码实在调用request.getHeader时进行的,这个方法将byte到char的转化使用的是ISO-8859-1,不能手动设置Header的其他解码格式,如果有非ASCII字符肯定会有乱码;
    • 不要在header中传递非ASCII字符,如果一定要出传递,可以先将这些字符用org.apache.catalina.util.URLEncoder编码,然后再添加到header中。
  • 访问数据库都是通过客户端JDBC驱动来完成的,用JDBC来存取数据要和数据的内置编码保持一致,可以通过设置JDBC URL来指定,如:MySQL:

jdbcUrl="jdbc:mysql://localhost:3306/boke?characterEncoding=utf-8"

JS中的编码问题
  • 外部引入JS文件

  • 如果script没有设置charset,浏览器就会以当前这个页面的默认字符集解析这个JS文件,如果外部的JS文件的编码格式与当前页面的编码格式一致,那么可以不设置这个charset,但是如果script.js文件的编码格式与当前页面不一致,上面的那段中文输入就会变成乱码。
  • JS的URL编码
    • 实际上JS中处理URL编码有三个函数,只要掌握了这三个函数,基本上就能正确处理JS的URL乱码问题了;
  1. escape()
  • 这个函数是将非ascii字符转化成Unicode编码值,并且在编码值前加上“%u”;
  1. 解码通过unescape()函数;
  2. 通过将特殊字符换成Unicode编码值可以避免因为编码的字符集的不兼容而出现的信息丢失问题,在服务端通过解码参数就可以避免乱码的问题。
    • encodeURL()
      • 与escape()相比,encodeURL()是真正的JS用来对URL编码的函数,它可以将整个URL中的字符(除了一些特殊字符,如:符号、数字、字母)进行UTF-8编码,在每个值之前加上“%”;
      • 解码通过encodeURL函数。
    • encodeURLComponent()
      • encodeURLComponent()这个函数比encodeURL()编码还要彻底;
      • 通常用于将一个URL当做参数放在另一个URL中;
    • 其他需要编码的地方
      • XML文件可以通过设置投来制定编码格式:
                       <?xml version"1.0" encoding="UTF-8">
  • Velocity(基于Java的模板引擎)设置编码格式:
                       services.VelocityService.input.encoding=uft-8
  • Jsp设置编码格式:
                       <%@page contentType="text/html; charset=utf-8">

常见问题分析
  • 中文变成了看不懂的字符

  • 一个汉字变成一个问号
  • 一个汉字变成两个问号
  • 一种不正常的正确编码
    • 我们通过request.getParameter获取参数值时,直接调用:
String value = request.getParameter(name);
  • 会出现乱码,但是用如下方式:
String value = new String(request.getParameter(name).getBytes("ISO-8859-1"),"GBK");
  • 解析时取得的value会是正确的汉字字符。

原文发布于微信公众号 - 轮子工厂(Programmer-ing)

原文发表时间:2018-04-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java帮帮-微信公众号-技术文章全总结

Java面试系列8

? 一、heap和stack有什么区别 栈是一种线形集合,其添加和删除元素的操作应在同一段完成。 栈按照后进先出的方式进行处理。 堆是栈的一个组成元素 ...

3065
来自专栏jessetalks

Javascript基础回顾 之(二) 作用域

参数传递的问题   在Javascript中所有的参数传递都是按值传递的。也就是说把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。...

2816
来自专栏Young Dreamer

简易前端模板引擎

模板解析 解决的问题: 将data和js+html片段解析成html片段,完成赋值和js逻辑,如下所示: 输入: var data = {     name: ...

20810
来自专栏LIN_ZONE

PHP 反射的简单使用

1554
来自专栏架构说

声明和定义的区别(深入理解)

问题 声明和定义区别 definition declared 微信排版支持makdown语法不友好 可以查看原文链接 先看一下 例子1 编译有没有问题? cl...

27110
来自专栏青玉伏案

算法与数据结构(二) 栈与队列的线性和链式表示(Swift版)

数据结构中的栈与队列还是经常使用的,栈与队列其实就是线性表的一种应用。因为线性队列分为顺序存储和链式存储,所以栈可以分为链栈和顺序栈,队列也可分为顺序队列和链队...

18810
来自专栏spring源码深度学习

java基础thread——java5之后的多线程(浅尝辄止)

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个...

921
来自专栏漏斗社区

学会代码执行函数,让老哥带你勇闯天涯!

最近研究PHP的一些危险函数,先写下代码执行函数的归纳,主要是参考自官方手册的解读,并附上了一些dogBypass的一句话,为什么是dog呢?因为在我看来dog...

3726
来自专栏互联网大杂烩

Java内存溢出

Java的NIO支持直接内存使用,从堆外获得内存空间,由于直接内存没有被Java虚拟机完全托管,若使用不当,容易触发直接内存溢出。

962
来自专栏Java帮帮-微信公众号-技术文章全总结

json解析-开发必会

json解析 什么是JSON: JSON即JavaScript Object Natation, 它是一种轻量级的数据交换格式, 与XML一样, 是广泛被采用的...

3706

扫码关注云+社区

领取腾讯云代金券