• 深入分析Java Web中的中文编码问题


    编码的原因

    需要编码的原因可以总结为以下几条:

    • 在计算机中存储的最小单元是1个字节,即8个比特(bit),所以能表示的字符范围是0~255个
    • 人类要表示的符号太多,无法使用1个字节来完全表示

    所以char和byte之间必须编码

    常见编码格式

    目前的编码格式很多,如GB2312,GBK,UTF-8,UTF-16都可以表示汉字,那么使用哪种编码格式存储汉字呢?就需要考虑其他因素了,例如:是存储空间重要还是编码的效率重要

    ASCII码

    ASCII码共有128个,使用1个字节的低7位表示,0-31是控制字符如回车,换行,删除等,32-126是打印字符,可以通过键盘输入并显示出来

    ISO-8859-1

    ISO-8859-1在ASCII码基础上进行了拓展,可以覆盖大多数西欧字符。ISO-8859-1仍然是单字符编码,它总共有256个字符

    ASCII码和ISO-8859-1都无法表示中文汉字。如果在ISO-8859-1中编码中文会出现"?"

    GB2312

    GB2312是双字节编码,包含628个字符和6763个汉字

    GBK

    GBK是为了拓展GB2312,加入了更多个汉字,可以表示21003个汉字。它的编码和GB2312兼容,也就是说GB2312编码的汉字可以用GBK来解码,不会出现乱码

    GBK和GB2312都是双字节的编码,对于单字节的(如英文,空格,控制符等)使用1个字节编码,汉字使用2个字节

    GBK和GB2312转码过程中都是需要查询码表,从效率上来说,因为GBK表示的字符更多,所以效率要略低于GB2312

    GB18030

    GB18030是国家标准,它的编码也与GB2312兼容,但是使用并不广泛

    UTF-16

    UTF就是Unicode(Universal Code 统一码),ISO试图创建一个全新的超语言字典,世界上所有的语言都可以通过这个字典互相翻译。

    UTF-16固定采用两个字节表示Unicode的转化格式,采用定长的方法,无论深入字符都采用两个字符表示,两个字节即16个bit,所以叫UTF-16

    定长的方式使得转码很快(因为格式固定,规则简单),但浪费了空间。Java内存中的编码使用的就是UTF-16

    UTF-8

    UTF-8采用一种变长技术,每个区域有不同的字码长度,不同的字符采用1~6个字节表示。英文采用1个字节,汉字采用3个字节

    UTF-16采用顺序编码,不能对单个字符的编码值进行校验,如果中间一个字符值损坏,后面所有的码值都会受到影响,而UTF-8不存在这样的问题

    UTF-8编码与GBK和GB2312不同,不用查码表,所以UTF-8编码效率要高于它们,所以在存储中文字符时采用UTF-8比较理想

    在几种编码格式比较

    GBK和GB2312规则类似,但是GBK范围更大,能够处理大多数的汉字字符,所以GBK和GB2312进行比较,应该选择GBK

    UTF-16和UTF-8都是处理Unicode编码,但是它们编码规则不同,相对而言UTF-16编码效率要高,从字符到字节之间互相转换更简单,适合在磁盘和内存之间使用,可以进行字符和字节之间快速切换,如Java内存编码就是使用的UTF-16编码。但是它不适合在网络之间传输,因为网络传输容易损坏字节流,一旦字节流损坏很难恢复,所以UTF-8更适合网络传输

    UTF-8对ASCII码采用单字节存储,另外单个字符出现损坏才会影响后面的其他字符。在编码效率上介于GBK和UTF-16字节,在编码效率上和编码安全上做了平衡,应优先使用

    Java中采用编码的场景

    在I/O操作存在的编码

    涉及到编码的地方一般都在字符到字节或者字节到字符的场景上,这种转换场景主要是I/O,包括磁盘I/O和网络I/O

    InputStreamReader类负责在I/O过程中字节到字节的转换

    OutputStreamWriter类负责在I/O过程中字符到字节的转换

    在使用这两个类时推荐使用两个参数的构造,并在第二个参数指定编码集

    String charset = "UTF-8";
    new InputStreamReader(inputStream, charset);
    new OutputStramWriter(outputStream, charset);
    

    只要使用统一编码Charset编码集,一般都不会出现乱码

    强烈不建议使用操作系统默认的编码,即构建对象时候不指定编码集,因为这样让程序的编码环境和运行环境绑定,在跨环境时很可能出现乱码问题

    在内存操作中的编码

    除了I/O操作,还有就是在内存中进行字符到字节的数据类型转换。在Java中用String表示字符串,所以String类提供了转换的方法

    String s = "中文字符串";
    byte[] b = s.getBytes("UTF-8");
    String str = new String(b, "UTF-8");
    

    Java Web中涉及到的编码

    Get请求:url中的中文会以"%"+编码的形式出现,是因为浏览器会将非ASCII码字符编码成16进制,并在16进制字节前加上"%"。解决方法:一是修改web容器(如Tomcat,Tomcat8及之后的版本默认用的UTF-8,低版本使用的是ISO-8859-1)默认的URIEncoding,再就是在后端代码中获取到参数之后手动解码(在Java端有处理URL编码两个类:java.net.URLEncodingjava.net.URLDecoding);二者推荐使用后者,但是强力建议不要在Get请求中传输中文

    POST请求:请求体中的中文参数可以通过request.setCharacterEncoding方法设置后,获得的参数就是中文了。一般来说框架中都会通过一个Filter的方式设置,开发时无需关心POST中的中文,但是对于GET中的中文则不适用,所以再次强力建议不要在Get请求中传输中文参数

    HTML页面通过meta设置编码,HTML5则默认使用UTF-8

    数据库通过客户端的JDBC驱动完成连接,JDBC驱动通过JDBC URL设置编码,如MySQL:url=jdbc:mysql:jdbc:///DB?userUnicode=true&characterEncoding=UTF-8

    引入外部JS文件时,指定该文件的编码:<scripte src="" charset="utf-8">

    XML,JSP,Velocity指定编码格式

    <?xml version="1.0" encoding="UTF-8"?>
    
    <%@page contentType="text/html;charset=UTF-8"%>
    
    services.VelocityService.input.encoding=UTF-8
    
  • 相关阅读:
    springboot成神之——websocket发送和请求消息
    springboot成神之——发送邮件
    springboot成神之——spring文件下载功能
    springboot成神之——spring的文件上传
    springboot成神之——basic auth和JWT验证结合
    springboot成神之——Basic Auth应用
    leetcode-easy-array-122 best time to buy and sell stocks II
    leetcode-easy-array-31 three sum
    leetcode-mid-others-621. Task Scheduler
    leetcode-mid-math-371. Sum of Two Integers-NO-???
  • 原文地址:https://www.cnblogs.com/lz2017/p/13771943.html
Copyright © 2020-2023  润新知