编码的原因

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

  • 在计算机中存储的最小单元是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