您的位置:首页 > 编程语言 > Java开发

Java编解码问题全解析(一)

2018-01-28 18:14 225 查看
乱码问题在Java开发中总是会碰到,spy在本博客讲述了乱码的本质,编解码中的基础概念,常见的几种编码格式及区别,最后介绍了在Java中涉及编解码操作的4个主要场景:内存,I/O操作、Java Web、数据库。

一、基础概念

二、常见的几种编码解析

- ASCII

- ISO8859_1 (扩展ASCII编码)

- GB2312/GBK

- GB18030

- UniCode

- UTF系列

三、Java中涉及编码、解码的4个场景

1. 内存

2. I/O 操作

3. Java Web-JSP/Servlet 中的编码方式
3.1 Jsp编译

3.2 Jsp输出

3.3 Meta 设置

3.4 Form 设置

3.5 servlet 中的设置

4. 数据库

一、基础概念

字节Byte 是存储容量的基本单位(B),1字节 = 8bit

字符 是指计算机中使用的文字和符号,比如1、2、3、A、b、(、)

编码、解码

编码是信息从一种形式转换为另一种形式的过程。不同字符对应的二进制数的规则。

解码是编码的逆过程,它是将存储在计算机的二进制转换为可以看到的字符。

乱码,本质是编码解码采用的格式不统一造成的。

字符集 字符编码的集合就是字符集,每个字符集包含的个数不同,计算机处理各种字符集文字,便需要进行编码。常见的字符集名称有:ASCII字符集、GB2312字符集、GB18030字符集、Unicode字符集、GBK字符集等。

二、常见的几种编码解析

- ASCII

单字节编码,用一个字节的低 7 位表示,该字符集共有 128 个字符。其中,0~31 是控制字符如换行、回车、删除等;32~126 是打印字符,可以通过键盘输入并且能够显示出来。 

- ISO8859_1 (扩展ASCII编码)

它是ISO 组织在 ASCII 码基础上制定了一些列标准用来扩展 ASCII 编码,它们是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵盖了大多数西欧语言字符,所以应用的最广泛。依然属于单字节编码,最多能表示256个字符,范围是0-255,很明显,Iso8859_1编码表示的范围很窄,无法表示中文字符。但是,由于是单字节编码,和计算机最基础的表示单位一致,很多协议默认使用该编码,如HTTP协议、浏览器的默认编解码方式。

- GB2312/GBK

GB2312,双字节编码,共收录了6763个汉字,是汉字的国际码,专门用来表示汉字,是中国国家标准简体中文字符集,其英文字母同Iso8859_1(兼容Iso8859_1),即单字节,故而GB2312 并非定长编码。GBK是汉字内码扩展规范,K 为汉语拼音 Kuo Zhan(扩展)中“扩”字的声母,编码用来同时表示繁体字和简体字,共收录了21003个字节,而GB2312只能表示简体字,GBK是兼容GB2312的。

- GB18030

是我国的强制标准,可能是单字节、双字节或者四字节编码,它兼容 GB2312 编码,这个虽然是国家标准,但是实际应用系统中使用的并不广泛。GB18030有两个版本,GB18030-2000, GB18030-2005,GB18030-2000包括27553个汉字、而GB18030-2005在2000基础上增加了42711个汉字。

- UniCode

这是最统一的编码,又称统一码、万国码、单一码,它是业界的一种标准,是为了解决传统的字符编码方案的局限而产生的,用来表示所有语言的字符,是定长双字节(也有四字节)编码,包括英文字母在内也是双字节编码,所以它是不兼容Iso8859_1编码的,也不兼容任何编码。不过,相对于iso8859_1,unicode编码只是在前面加了一个 0 字节。比如字符a 为 00 61。

需要说明的是,定长编码便于计算机处理(GB2312/GBK不是定长编码),而unicode编码又可以表示所有字符,所以在很多软件内部都是使用unicode编码来处理的,比如Java。

- UTF系列

考虑到unicode编码不兼容iso8859-1编码,而且容易占用更多的空间,因为对于英文字母,unicode也需要两个字节表示,所以unicode不便于传输和存储,因此产生了UTF编码,它是unicode的实现方式,Unicode Transformation Format(简称 UTF)。UTF编码兼容iso8859-1编码,同时也可以用来表示所有语言的字符,不过utf 是不定长编码,每个字符的长度从1-6个字节不等。另外,utf编码自带简单的校验功能,一般来讲,英文字母都用一个字节表示,而汉字使用3个字节。

注意,虽然说utf是为了使用更少的空间而使用的,但那只是相对于unicode编码来说,如果已经知道是汉字, 则使用GB2312、GBK无疑是最节省的。

- unicode是字符集,它主要有utf-8/utf-16/utf-32三种实现方式。Java的class文件使用utf-8编码方式,JVM运行时采用utf-16。

UTF-16

UTF-16 具体定义了 Unicode 字符在计算机中存取方法。UTF-16 用两个字节来表示 Unicode 转化格式,这个是定长的表示方法,不论什么字符都可以用两个字节表示,两个字节是 16 个 bit,所以叫 UTF-16。UTF-16 表示字符非常方便,每两个字节表示一个字符,这个在字符串操作时就大大简化了操作,这也是 Java 以 UTF-16 作为内存的字符存储格式的一个很重要的原因。

UTF-8

UTF-8 采用了一种变长技术,每个编码区域有不同的字码长度。不同类型的字符可以是由 1~6 个字节组成。

对于单字节的字符,字节的第一位设为0,后面7位为这个符号的unicode码,因此,对于英文字母,utf-8编码和ASCII码相同。

对于n字节的字符(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10,剩下的全部为这个符号的unicode编码。

示例:已知 “严” 的unicode编码是 4E 25(1001110 00100101),它的utf-8编码需要3个字节,格式是**111**0xxxx 10xxxxxx 10xxxxxx,然后从“严”的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0,故而得到的“严”的UTF-8编码为 11100100 10111000 10100101,转换为16进制为E4B8A5。

所以:(1)如果一个字节,最高位(第 8 位)为 0,表示这是一个 ASCII 字符(00 - 7F);(2)如果一个字节,以 11 开头,连续的 1 的个数暗示这个字符的字节数,例如:110xxxxx 代表它是双字节 UTF-8 字符的首字节;(3)如果一个字节,以 10 开始,表示它不是首字节,需要向前查找才能得到当前字符的首字节 。

UTF-32 对每个字符都使用4字节进行编码。

三、Java中涉及编码、解码的4个场景

1. 内存

Char、String、byte。char是一个unicode字符,为16位的整数。byte是字节,字符串在网络传输或存储需要转换为byte数组。在从网络接收或从存储设备读取后需要将byte数组转换为String。String 可以看为char组成的数组。String和char为内存形式,byte是网络传输或存储的序列化形式。

String wo = "我";
char woC = wo.charAt(0);
String woHex = Integer.toHexString(woC);//6211
//java内存中
byte[] woUnicodeBytes = wo.getBytes("unicode");//or UTF-16,结果一样
String woUniCodeByteHex = DatatypeConverter.printHexBinary(woUnicodeBytes);//FEFF 6211
byte[] woUTF_8Bytes =wo.getBytes("UTF-8");
String woUTF_8BytesHex = DatatypeConverter.printHexBinary(woUTF_8Bytes);//我的GBK编码 E6 88 91


我们在开发中,总是免不了对字符串的getBytes()、new String的过程,过程中会出现各种乱码问题。但初级开发人员请注意:Java提供的getBytes、new String方法都是提供了包含编码格式参数的,开发人员如果不写,则会采用操作系统当前的编码格式,getBytes()与getBytes(Charset.defaultCharset())等价,开发与部署的操作系统编码解码格式不一,便会出现各种乱码问题;故而建议大家显式地传入编码格式参数来使用这些方法。如,

String inputData = new String(inputFromRequestBytes,"UTF-8");
//do sth;
String outputData = "我处理完了";
byte[] outputResponseBytes = outputData.getBytes("UTF-8");


补充:Java中,可以查看Charset类,JDK1.6支持162种字符集。可以通过Charset的avaiableCharsets拿到所有的java支持的字符集。

System.out.println(Charset.availableCharsets().size());//jdk 1.8 输出 169
Set<String> charSetNames = Charset.availableCharsets().keySet();

System.out.println(charSetNames.contains("UTF-8"));
System.out.println(charSetNames.contains("GBK"));


中文出现乱码情形

可参考这个:http://blog.csdn.net/lwj734114646/article/details/51312970

2. I/O 操作



I/O操作比内存唯一需要多注意的是,文件本身的编码格式。在操作系统下,手工创建的文件,如果不指定便是系统默认的编码格式,故而为避免因操作系统不同而带来的中文乱码问题,最好在文件写入和读出的时候显式指定编码格式。

java读取本地文件

public static String readFile(String fileName) {
String fileContent = "";
try {
File f = new File(fileName);
if (f.isFile() && f.exists()) {
InputStreamReader read = new InputStreamReader(
new FileInputStream(f), "gbk");
BufferedReader reader = new BufferedReader(read);
String line;
while ((line = reader.readLine()) != null) {
fileContent += line+"\n";
}
read.close();
}
} catch (Exception e) {
e.printStackTrace();
}
return fileContent;
}


java写入本地文件

public static void writeFile(String fileName, String fileContent) {
try {
File f = new File(fileName);
if (!f.exists()) {
f.createNewFile();
}
OutputStreamWriter write = new OutputStreamWriter(
new FileOutputStream(f), "gbk");
BufferedWriter writer = new BufferedWriter(write);
writer.write(fileContent);
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
}


3. Java Web-JSP/Servlet 中的编码方式

3.1 Jsp编译

<%@page pageEncoding=”GBK”%>,jsp编译成servlet源代码文件时所使用的编码,指定文件的存储编码,很明显,该设置应该置于文件的开头。

3.2 Jsp输出

<%@ page contentType=”text/html; charset= GBK” %>,指定文件输出到browser时使用的编码,该设置也应该置于文件的开头。该设置和response.setCharacterEncoding(“GBK”)等效,即服务器响应(response)的编码方式。

3.3 Meta 设置

< META http-equiv=”Content-Type” content=”text/html; charset=GBK” />

指定网页使用的编码,该设置对静态网页尤其有作用。因为静态网页无法采用jsp的设置,而且也无法执行response.setCharacterEncoding()。例如:

  如果同时采用了jsp输出和meta设置两种编码指定方式,则jsp指定的优先。因为jsp指定的直接体现在response中。

  需要注意的是,apache有一个设置可以给无编码指定的网页指定编码,该指定等同于jsp的编码指定方式,所以会覆盖静态网页中的meta指定。所以有人建议关闭该设置。

3.4 Form 设置

,当浏览器提交表单的时候,可以指定相应的编码。一般不必不使用该设置,浏览器会直接使用网页的编码。

3.5 servlet 中的设置

setCharacterEncoding(),该函数用来设置http请求或者相应的编码。

a)请求:request.setCharacterEncoding(“GBK”),设置对客户端请求数据的编码的方式,指定后可以通过getParameter()则直接获得正确的字符串,如果不指定,则默认使用iso8859-1编码,需要进一步处理(如:iso8859_1解码,GBK编码)。值得注意的是在执行setCharacterEncoding()之前,不能执行任何getParameter()。java doc上说明:This method must be called prior to reading request parameters or reading input using getReader()。而且,该指定只对POST方法有效,对GET方法无效。分析原因,应该是在执行第一个getParameter()的时候,java将会按照编码分析所有的提交内容,而后续的getParameter()不再进行分析,所以setCharacterEncoding()无效。而对于GET方法提交表单是,提交的内容在URL中,一开始就已经按照编码分析所有的提交内容,setCharacterEncoding()自然就无效。

b)响应:response.setCharacterEncoding(“GBK”),其作用是指定服务器响应的编码方式,服务器在将数据发送到浏览器之前,对数据进行重新编码时使用的编码方式,同时,该设置会传递给浏览器,告诉浏览器输出内容所采用的编码。

4. 数据库

后续补充

部分参考:https://www.cnblogs.com/gdayq/p/5817367.html

此博主讲的不错:https://www.cnblogs.com/x_wukong/p/3675832.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息