Java编码问题汇总

工做中常常遇到java编码问题,因为缺少研究,老是没法给出确切的答案,这个周末在网上查了一些资料,在此作些汇总。html

    问题一:在java中读取文件时应该采用什么编码?java

Java读取文件的方式整体能够分为两类:按字节读取和按字符读取。按字节读取就是采用InputStream.read()方法来读取字节,而后保存到一个byte[]数组中,最后常常用new String(byte[]);把字节数组转换成String。在最后一步隐藏了一个编码的细节,new String(byte[]);会使用操做系统默认的字符集来解码字节数组,中文操做系统就是GBK。而咱们从输入流里读取的字节极可能就不是GBK编码的,由于从输入流里读取的字节编码取决于被读取的文件自身的编码。举个例子:咱们在D:盘新建一个名为demo.txt的文件,写入咱们。,并保存。此时demo.txt编码是ANSI,中文操做系统下就是GBK。此时咱们用输入字节流读取该文件所获得的字节就是使用GBK方式编码的字节。那么咱们最终new String(byte[]);时采用平台默认的GBK来编码成String也是没有问题的(字节编码和默认解码一致)。试想一下,若是在保存demo.txt文件时,咱们选择UTF-8编码,那么该文件的编码就不在是ANSI了,而变成了UTF-8。仍然采用输入字节流来读取,那么此时读取的字节和上一次就不同了,此次的字节是UTF-8编码的字节。两次的字节显然不同,一个很明显的区别就是:GBK每一个汉字两个字节,而UTF-8每一个汉字三个字节。如何咱们最后还使用new String(byte[]);来构造String对象,则会出现乱码,缘由很简单,由于构造时采用的默认解码GBK,而咱们的字节是UTF-8字节。正确的办法就是使用new String(byte[],”UTF-8”);来构造String对象。此时咱们的字节编码和构造使用的解码是一致的,不会出现乱码问题了。api

 

说完字节输入流,再来讲说字节输出流。数组

咱们知道若是采用字节输出流把字节输出到某个文件,咱们是没法指定生成文件的编码的(假设文件之前不存在),那么生成的文件是什么编码的呢?通过测试发现,其实这取决于写入的字节编码格式。好比如下代码:浏览器

OutputStream out = new FileOutputStream("d:\\demo.txt");tomcat

out.write("咱们".getBytes());jvm

getBytes()会采用操做系统默认的字符集来编码字节,这里就是GBK,因此咱们写入demo.txt文件的是GBK编码的字节。那么这个文件的编码就是GBK。若是稍微修改一下程序:out.write("咱们".getBytes(“UTF-8”));此时咱们写入的字节就是UTF-8的,那么demo.txt文件编码就是UTF-8。这里还有一点,若是把咱们换成123abc之类的ascii码字符,那么不管是采用getBytes()或者getBytes(“UTF-8”)那么生成的文件都将是GBK编码的。jsp

这里能够总结一下,InputStream中的字节编码取决文件自己的编码,而OutputStream生成文件的编码取决于字节的编码。ide

 

下面说说采用字符输入流来读取文件。工具

首先,咱们须要理解一下字符流。其实字符流能够看作是一种包装流,它的底层仍是采用字节流来读取字节,而后它使用指定的编码方式将读取字节解码为字符。提及字符流,不得不提的就是InputStreamReader。如下是java api对它的说明: InputStreamReader是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集能够由名称指定或显式给定,不然可能接受平台默认的字符集。说到这里其实很明白了,InputStreamReader在底层仍是采用字节流来读取字节,读取字节后它须要一个编码格式来解码读取的字节,若是咱们在构造InputStreamReader没有传入编码方式,那么会采用操做系统默认的GBK来解码读取的字节。还用上面demo.txt的例子,假设demo.txt编码方式为GBK,咱们使用以下代码来读取文件:

InputStreamReader  in = new InputStreamReader(new FileInputStream(“demo.txt”));

那么咱们读取不会产生乱码,由于文件采用GBK编码,因此读出的字节也是GBK编码的,而InputStreamReader默认采用解码也是GBK。若是把demo.txt编码方式换成UTF-8,那么咱们采用这种方式读取就会产生乱码。这是由于字节编码(UTF-8)和咱们的解码编码(GBK)形成的。解决办法以下:

InputStreamReader  in = new InputStreamReader(new FileInputStream(“demo.txt”),”UTF-8”);

InputStreamReader指定解码编码,这样两者统一就不会出现乱码了。

 

下面说说字符输出流。

字符输出流的原理和字符输入流的原理同样,也能够看作是包装流,其底层仍是采用字节输出流来写文件。只是字符输出流根据指定的编码将字符转换为字节的。字符输出流的主要类是:OutputStreamWriterJava api解释以下:OutputStreamWriter 是字符流通向字节流的桥梁:使用指定的 charset 将要向其写入的字符编码为字节。它使用的字符集能够由名称指定或显式给定,不然可能接受平台默认的字符集。说的很明白了,它须要一个编码将写入的字符转换为字节,若是没有指定则采用GBK编码,那么输出的字节都将是GBK编码,生成的文件也是GBK编码的。若是采用如下方式构造OutputStreamWriter

OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(“dd.txt”),”UTF-8”);

那么写入的字符将被编码为UTF-8的字节,生成的文件也将是UTF-8格式的。

   

问题二: 既然读文件要使用和文件编码一致的编码,那么javac编译文件也须要读取文件,它使用什么编码呢?

       这个问题历来就没想过,也从没当作是什么问题。正是由于问题一而引起的思考,其实这里仍是有东西能够挖掘的。下面分三种状况来探讨,这三种状况也是咱们经常使用的编译java源文件的方法。

       1.javac在控制台编译java类文件。

       一般咱们手动创建一个java文件Demo.java,并保存。此时Demo.java文件的编码为ANSI,中文操做系统下就是GBK.而后使用javac命令来编译该源文件。”javac Demo.java”Javac也须要读取java文件,那么javac是使用什么编码来解码咱们读取的字节呢?其实javac采用了操做系统默认的GBK编码解码咱们读取的字节,这个编码正好也是Demo.java文件的编码,两者一致,因此不会出现乱码状况。让咱们来作点手脚,在保存Demo.java文件时,咱们选择UTF-8保存。此时Demo.java文件编码就是UTF-8了。咱们再使用”javac Demo.java”来编译,若是Demo.java里含有中文字符,此时控制台会出现警告信息,也出现了乱码。究其缘由,就是由于javac采用了GBK编码解码咱们读取的字节。由于咱们的字节是UTF-8编码的,因此会出现乱码。若是不信的话你能够本身试试。那么解决办法呢?解决办法就是使用javacencoding参数来制定咱们的解码编码。以下:javac -encoding UTF-8 Demo.java这里咱们指定了使用UTF-8来解码读取的字节,因为这个编码和Demo.java文件编码一致,因此不会出现乱码状况了。

 

       2.Eclipse中编译java文件。

       我习惯把Eclipse的编码设置成UTF-8。那么每一个项目中的java源文件的编码就是UTF-8。这样编译也从没有问题,也没有出现过乱码。正是由于这样才掩盖了使用javac可能出现的乱码。那么Eclipse是如何正确编译文件编码为UTF-8java源文件的呢?惟一的解释就是Eclipse自动识别了咱们java源文件的文件编码,而后采起了正确的encoding参数来编译咱们的java源文件。功劳都归功于IDE的强大了。

      

       3.使用Ant来编译java文件。

       Ant也是我经常使用的编译java文件的工具。首先,必须知道Ant在后台其实也是采用javac来编译java源文件的,那么可想而知,1会出现的问题在Ant中也会存在。若是咱们使用Ant来编译UTF-8编码的java源文件,而且不指定如何编码,那么也会出现乱码的状况。因此Ant的编译命令<javac>有一个属性” encoding”容许咱们指定编码,若是咱们要编译源文件编码为UTF-8java文件,那么咱们的命令应该以下:

       <javac destdir="${classes}" target="1.4" source="1.4" deprecation="off" debug="on" debuglevel="lines,vars,source" optimize="off" encoding="UTF-8">

       指定了编码也就至关于”javac –encoding”了,因此不会出现乱码了。

 

问题三:tomcat中编译jsp的状况。

       这个话题也是由问题二引出的。既然javac编译java源文件须要采用正确的编码,那么tomcat编译jsp时也要读取文件,此时tomcat采用什么编码来读取文件?会出现乱码状况吗?下面咱们来分析。

       咱们一般会在jsp开头写上以下代码:

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>

我经常不写pageEncoding这个属于,也不明白它的做用,可是不写也没出现过乱码状况。其实这个属性就是告诉tomcat采用什么编码来读取jsp文件的。它应该和jsp文件自己的编码一致。好比咱们新建个jsp文件,设置文件编码为GBK,那么此时咱们的pageEncoding应该设置为GBK,这样咱们写入文件的字符就是GBK编码的,tomcat读取文件时采用也是GBK编码,因此能保证正确的解码读取的字节。不会出现乱码。若是把pageEncoding设置为UTF-8,那么读取jsp文件过程当中转码就出现了乱码。上面说我经常不写pageEncoding这个属性,可是也没出现过乱码,这是怎么回事呢?那是由于若是没有pageEncoding属性,tomcat会采用contentTypecharset编码来读取jsp文件,个人jsp文件编码一般设置为UTF-8,contentTypecharset也设置为UTF-8,这样tomcat使用UTF-8编码来解码读取的jsp文件,两者编码一致也不会出现乱码。这只是contentTypecharset的一个做用,它还有两个做用,后面再说。可能有人会问:若是我既不设置pageEncoding属性,也不设置contentTypecharset属性,那么tomcat会采起什么编码来解码读取的jsp文件呢?答案是iso-8859-1,这是tomcat读取文件采用的默认编码,若是用这种编码来读取文件显然会出现乱码。

   

    问题四:输出。

问题二和问题三分析的过程其实就是从源文件àclass文件过程当中的转码状况。最终的class文件都是以unicode编码的,咱们前面所作的工做就是把各类不一样的编码转换为unicode编码,好比从GBK转换为unicode,UTF-8转换为unicode。由于只有采用正确的编码来转码才能保证不出现乱码。Jvm在运行时其内部都是采用unicode编码的,其实在输出时,又会作一次编码的转换。让咱们分两种状况来讨论。

1.java中采用Sysout.out.println输出。

好比:Sysout.out.println(“咱们”)。通过正确的解码后咱们unicode保存在内存中的,可是在向标准输出(控制台)输出时,jvm又作了一次转码,它会采用操做系统默认编码(中文操做系统是GBK),将内存中的unicode编码转换为GBK编码,而后输出到控制台。由于咱们操做系统是中文系统,因此往终端显示设备上打印字符时使用的也是GBK编码。由于终端的编码没法手动改变,因此这个过程对咱们来讲是透明的,只要编译时能正确转码,最终的输出都将是正确的,不会出现乱码。在Eclipse中能够设置控制台的字符编码,具体位置在Run Configuration对话框的Common标签里,咱们能够试着设置为UTF-8,此时的输出就是乱码了。由于输出时是采用GBK编码的,而显示倒是使用UTF-8,编码不一样,因此出现乱码。

 

2.jsp中使用out.println()输出到客户端浏览器。

Jsp编译成class后,若是输出到客户端,也有个转码的过程。Java会采用操做系统默认的编码来转码,那么tomcat采用什么编码来转码呢?其实tomcat是根据<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>contentTypecharset参数来转码的,contentType用来设置tomcat往浏览器发送HTML内容所使用的编码。Tomcat根据这个编码来转码内存中的unicode。通过转码后tomcat输出到客户端的字符编码就是utf-8了。那么浏览器怎么知道采起什么编码格式来显示接收到的内容呢?这就是contentTypecharset属性的第三个做用了:这个编码会在HTTP响应头中指定以通知浏览器。浏览器使用http响应头的contentTypecharset属性来显示接收到的内容。

总结一下contentType charset的三个做用:

1).在没有pageEncoding属性时,tomcat使用它来解码读取的jsp文件。

2).tomcat向客户端输出时,使用它来编码发送的内容。

3).通知浏览器,应该以什么编码来显示接收到的内容。

为了能更好的理解上面所说的解码和转码过程,咱们举一个例子。

新建一个index.jsp文件,该文件编码为GBK,jsp开头咱们写上以下代码:

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="GBK"%>

这里的charsetpageEncoding不一样,可是也不会出现乱码,我来解释一下。首先tomcat读取jsp内容,并根据pageEncoding指定的GBK编码将读取的GBK字节解码并转换为unicode字节码保存在class文件中。而后tomcat在输出时(out.println())使用charset属性将内存中的unicode转换为utf-8编码,并在响应头中通知浏览器,浏览器以utf-8显示接收到的内容。整个过程没有一次转码错误,因此就不会出现乱码状况。

 

    问题五:PropertiesResourceBundle使用的解码编码。

                以上两个是咱们经常使用的类,他们在读取文件过程当中并不容许咱们指定解码编码,那么它们采起什么解码方式呢?查看源码后发现都是采用 iso-8859-1 编码来解码
           的。这样的话咱们也不难理解咱们写的
properties 文件为何都是 iso-8859-1 的了。由于采起任何一个别的编码都将产生乱码。由于 iso-8859-1 编码是没
           有中文的,因此咱们输入的中文要转换为
unicode ,一般咱们使用插件来完成,也可使用 jdk 自带的 native2ascii 工具。
相关文章
相关标签/搜索