文章内容只是个人认识,用于整理和日后回忆。
Java的编码问题
Java基础编码
先简单叙述Java的拜尼马相关情况。
JVM的字节序
Java比较特殊,他不像大部分的程序依靠机器本身的字节序,而是统一采用大端模式。
Java的编码
Java在运行时,统一采用Unicode编码方式,其实就是采用UTF-16编码。
Java与外部对接时的编码
比如说在输出字符串到控制台时,Java会自动将内存中的Unicode编码转换成系统当前所处于的编码格式,在中国,大部分电脑都是采用的GBK格式,所以就会将字符串从Unicode编码转换成GBK编码输出,从而保证不会出现乱码。
Java执行的编码
执行的编码与内存中的编码并没有关系,执行的编码意味着Java在读取Java文件时,默认Java文件的编码格式。比如Java文件是UTF-8的,如果虚拟机意味是GBK,那么对于ASCII字符来说没有影响,但是对于中文字符,就必然会导致乱码。
Java在执行时,会从当前的系统获取系统的编码方式,作为执行的默认编码,所以,如果编码方式不同时,一定要改变Java的执行参数。而且要在编译和执行两个命令中,都要修改编码方式,在编译时需要增加-encoding <编码>
参数,执行时需要在增加-Dfile.encoding=<编码>
参数,如:
javac -encoding UTF-8 Main.java
java Main -Dfile.encoding=UTF-8
执行时,需要注意参数放在类名后面,否则虚拟机或错误识别类的名字从而找不到程序入口,就会报错,找不到或无法加载主类。
再有一个找不到或无法加载主类的原因是,环境变量配置问题,eclipse会自动识别Java的基础jar包,但是命令行不会,所以必须主动配置好。尤其是
CLASSPATH
要保证将rt.jar
和tools.jar
都放进去,这两个jar包一般都是在%JAVA_HOME%/lib
和%JAVA_HOME%/jre/lib
文件夹下。而且还要加上.
,即是当前目录。
Java编码的操作
一些实际的Java编码操作。
Java常见的字符按编码和解码
- 编码
"string".getBytes(“charset”);
通过这种方式,可以获得字符串的特定编码方式的字节数组。 如果不传参数,会自动获取Java执行编码方式作为默认参数。 - 解码
new String(bytes, "charset);
通过这种方式,将字符串以特定编码方式的解读为字符串。同上,如果不传编码参数,会自动获取Java执行编码方式,作为默认参数。
这里其实很容易糊涂,要清楚,在计算机存储的一定是字节而不是字符串所显示的字符本身。字符串本身在Java运行中的编码凡是都是Unicode,而想要获得不同编码的字节数组时,其实时两种编码格式的转换,而不是简单意义上的将字符变成字节。除了显示的时候,计算机中,是不存储字符的。
所以编码操作执行时,是将内存中,对应字符串的Unicode字节数组,转换成指定编码的字节数组。而节码操作,是将指定字节数组,以指定编码方式转换为Unicode字节数组,存储到内存中。
实际例子
举例说明,编码操作的使用。其实,出现乱码等问题,一般都是编码和解码不匹配造成的。
简单项目的乱码
简单项目的乱码,一般都是编译或是执行时的编码和源文件的编码格式不一致造成。只要保证源文件不乱码,三个编码格式一致,正常情况下,就不会出现乱码。
不正常的情况,特指使用了编码和解码操作,这两个操作使用时,一定要清楚的直到为什么要用他们,每一次操作时,对应的编码状态是什么样的。
WEB项目中的乱码问题
起点
这个的乱码问题,其实根源就是出现了三个相对独立的编码模块。一个是程序本身,一个时浏览器,一个时中间件。当然,这里要先保证没有出现简单项目乱码中的问题。
各自的编码
程序本身,前面已经说过,在内存中是采用的Unicode编码格式;浏览器,默认采用系统的编码格式,所以如果你是跨区域项目,一定要考虑好你的编码显示问题;中间件,以tomcat为例,默认用ISO-8859-1
作为传输数据的编码格式。
这里要注意,中间件的传输数据的编码是默认用
ISO-8859-1
,并不代表它执行时是也是该编码。
网页数据
将网页数据单独拎出来说,是因为他相对ajax
等方式的数据传输,问题要简单的多。网页数据传输,默认也是ISO-8859-1
,而且是在浏览器和传输过程中都默认是该编码,所以经常遇到显示中文时,常常会直接指定网页的编码格式,设置在网页中的meta
标签等位置。这时,浏览器会优先以网页中指定的编码解析网页,所以一般是较少出现乱码问题的,出现乱码问题也相对容易修正。
文本数据
文本数据的传输过程:先是由软件将Unicode字符串转换成tomcat的传输编码,然后由tomcat发送到浏览器,再将其转换成浏览器的默认编码,再由浏览器翻译为Unicode字节数组放入内存,显示时根据当前系统编码显示出来。可以看出有以下几个可能会出现问题:
ISO-8859-1
不支持中文字符,而且是用单个字节表示字符。
这种情况下直接将字符串给tomcat传输,必然会出错。所以通常会将传输的数据用浏览器端默认的编码方式处理成字节数组,然后再用ISO-8859-1
读取到内存中,这个时候直接输出字符串显然会是乱码,但是发送到浏览器端后,显示出来的却会是正确结果。因为浏览器会先用制定好的格式来读取字节数组并载入到内存中,用于后来显示。举例说明:
PrintWriter out = response.getWriter();
//假设浏览器端系统默认编码为gbk
out.println(new String("你好".getBytes("gbk"), "ISO-8859-1"));
在这个过程中
ISO-8859-1
的作用其实只是为了保证传输的可靠,因为他是单字节的,并且所有数字都有对应字符。保证了任何单个字节都可以被ISO-8859-1
编码读取,并传输,当然,这时大部分情况,直接输出都是乱码,因为它并不是这些字节的实际编码。而且传输时,它会保证数据以字符串的实际编码的字节数组传输,因为tomcat要传输的毕竟还是二进制的字节数组,所以相当于让字符串经历ISO-8859-1
编码和解码,等于没有变化。这时浏览器直接以系统编码获取正确结果就行了。但是,新的问题出现了。
- 浏览器所在系统的编码未必统一。
出现这种情况,显然是比较麻烦的,因为针对每个环境的浏览器单独写一个业务,太麻烦了。但这确实绝大部分程序实际项目都要考虑的。所以有了对于头信息的应用,我们完全可以直接告诉浏览器我们传输过去的数据是什么编码方式,然后让浏览器跟随服务器来完成显示任务。response.setContentType("text/html;charset=utf-8")
该操作将会指定返回数据的头信息的编码方式为utf-8
。设定之后,程序还会自动设定传输时的数据不再时ISO-8859-1
而是采用跟该头信息一致的编码方式,这样显然就方便很多,也不需要再用getBytes
等方法来回解码编码。而浏览器也会先读取头文件,再使用头文件指定的编码方式载入数据。至此保证了各种环境下的编码问题,大部分的框架结构也都时采用该方式,提前设置头信息,保证数据的可读性。
response.setContentType("text/html;charset=utf-8");