`

Java编码问题汇总(转)

    博客分类:
  • Java
阅读更多

工作中经常遇到 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 对象。此时我们的字节编码和构造使用的解码是一致的,不会出现乱码问题了。

 

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

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

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

out.write(" 我们 ".getBytes());

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

这里可以总结一下, InputStream 中的字节编码取决文件本身的编码,而 OutputStream 生成文件的编码取决于字节的编码。

 

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

首先,我们需要理解一下字符流。其实字符流可以看做是一种包装流,它的底层还是采用字节流来读取字节,然后它使用指定的编码方式将读取字节解码为字符。说起字符流,不得不提的就是 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 指定解码编码,这样二者统一就不会出现乱码了。

 

下面说说字符输出流。

字符输出流的原理和字符输入流的原理一样,也可以看做是包装流,其底层还是采用字节输出流来写文件。只是字符输出流根据指定的编码将字符转换为字节的。字符输出流的主要类是: OutputStreamWriter Java 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 编码的,所以会出现乱码。如果不信的话你可以自己试试。那么解决办法呢?解决办法就是使用 javac encoding 参数来制定我们的解码编码。如下: javac -encoding UTF-8 Demo.java 这里我们指定了使用 UTF-8 来解码读取的字节,由于这个编码和 Demo.java 文件编码一致,所以不会出现乱码情况了。

 

       2.Eclipse 中编译 java 文件。

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

      

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

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

       <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 会采用 contentType charset 编码来读取 jsp 文件,我的 jsp 文件编码通常设置为 UTF-8,contentType charset 也设置为 UTF-8, 这样 tomcat 使用 UTF-8 编码来解码读取的 jsp 文件,二者编码一致也不会出现乱码。这只是 contentType charset 的一个作用,它还有两个作用,后面再说。可能有人会问:如果我既不设置 pageEncoding 属性,也不设置 contentType charset 属性,那么 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"%> contentType charset 参数来转码的, contentType 用来设置 tomcat 往浏览器发送 HTML 内容所使用的编码。 Tomcat 根据这个编码来转码内存中的 unicode 。经过转码后 tomcat 输出到客户端的字符编码就是 utf-8 了。那么浏览器怎么知道采取什么编码格式来显示接收到的内容呢?这就是 contentType charset 属性的第三个作用了:这个编码会在 HTTP 响应头中指定以通知浏览器。浏览器使用 http 响应头的 contentType charset 属性来显示接收到的内容。

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

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

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

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

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

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

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

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

 

    问题五: Properties ResourceBundle 使用的解码编码。

               以上两个是我们常用的类,他们在读取文件过程中并不允许我们指定解码编码,那么它们采取什么解码方式呢?查看源码后发现都是采用 iso-8859-1 编码来解码
           的。这样的话我们也不难理解我们写的
properties 文件为什么都是 iso-8859-1 的了。因为采取任何一个别的编码都将产生乱码。因为 iso-8859-1 编码是没
           有中文的,所以我们输入的中文要转换为
unicode ,通常我们使用插件来完成,也可以使用 jdk 自带的 native2ascii 工具。
分享到:
评论

相关推荐

    Java开发乱码问题解决方法汇总

    Java开发乱码问题解决方法汇总 Java开发中乱码问题是非常常见的问题之一,而解决这些问题需要具备一定的技术知识和经验。在本文中,我们将总结一些常见的Java开发乱码问题解决方法,希望能够为读者提供帮助。 1. ...

    Java常见问题集锦

    ### Java常见问题集锦 #### 1. Java2 (JDK 1.2) 的环境配置 - **问题描述**:如何在不同操作系统上正确配置Java2(即JDK 1.2)的环境变量? - **解决方案**: - **Solaris平台**: - 设置`JAVA_HOME`为Java2的...

    Java开发常见问题汇总.ppt

    在Java开发过程中,遇到的问题多种多样,从环境配置到代码编写,每个环节都有可能出现问题。以下是一些关于Java开发的常见问题及其解决方法。 首先,确保Java开发环境的正确安装和配置。在Windows系统中,可以通过...

    Java路径问题汇总.pdf

    Java路径问题在编程中是一个常见的挑战,特别是在处理文件读写、资源加载等操作时。Java的路径处理涉及多个类和方法,如`File`, `URL`, `URI`等,它们在处理路径时有不同的规则和行为。 1. **URL与空格问题**: 当...

    Java路径问题汇总[文].pdf

    Java的路径处理涉及到URL、URI、File等核心类的交互,而这些类在处理包含空格、特殊字符和编码的问题时会有特定的行为。 首先,Java的URL类在表示路径时会自动对空格、特殊字符进行编码。例如,空格会被转换为"%20...

    Java 项目经验汇总(简历项目素材)

    本资料"Java项目经验汇总(简历项目素材)"提供了丰富的实例和指导,帮助Java开发者构建出引人注目的简历项目部分。 首先,理解Java项目经验的重要性。Java是一种广泛应用于企业级应用开发的编程语言,其稳定性和...

    JAVA面试题总汇JAVA面试题总汇.doc

    【JAVA面试题总汇】 1. **final, finally, finalize的区别** - `final` 关键字用于声明不可变的变量、无法重写的类或方法。对于变量,一旦赋值后不可更改;对于类,表示该类不能被继承;对于方法,表示该方法不能...

    java面试资料汇总

    Java面试资料汇总的知识点可以从多个方面来总结: 面向对象的编程特性包括封装、继承、多态和抽象,它们是...以上是Java面试资料汇总中提及的部分知识点总结,更多详细内容需要结合具体问题和实际场景进行深入探讨。

    JAVA面试题资料汇总及答案整理.pdf

    Java是一种广泛使用的面向对象的编程语言...这些知识点涵盖了Java的基础语法、面向对象特性、内存管理、并发编程和异常处理等方面,是Java面试中常见的问题。理解并掌握这些概念对于成为一名优秀的Java开发者至关重要。

    Java英文单词汇总

    Java英文单词汇总 Java 英文单词汇总是 Java 语言中广泛使用的一些英文单词的总结,这些单词涵盖了 Java 中的大部分方法的名字。了解这些单词的含义可以帮助开发者更好地理解 Java 语言,并提高编程效率。 ...

    JAVA开发工具大汇总

    ### JAVA开发工具大汇总 #### 1. JDK (Java Development Kit) **简介**: JDK是由Sun Microsystems提供的免费Java开发工具包,随着Oracle收购Sun后继续维护和发展。它为开发者提供了丰富的语言特性和运行环境,同时...

    Java面试汇总.pdf

    - **面向过程**:强调的是执行流程,通过一系列步骤解决问题。适用于需要精确控制执行流程的应用场景,如嵌入式开发。 - **面向对象**:关注对象及其行为,利用封装、继承和多态等特性构建灵活且可扩展的系统结构。...

    java基础班汇总.pdf

    二进制数据转十进制数据采用的是8421编码方式,而十进制转二进制则通过不断除以2并获取余数来完成。此外,讲义中还介绍了计算机存储单位以及常用的DOS命令,这些命令是管理和操作文件系统时必不可少的工具。 关于...

    北京java面试题汇总

    【标题】:“北京java面试题汇总” 这是一份专门针对北京地区Java开发岗位面试的题库,涵盖了众多Java公司的常见面试问题。对于准备应聘Java工程师的求职者来说,这是一个宝贵的资源,可以帮助他们全面了解和复习...

    Java编程单词汇总,分25天

    8. **unicode** 和 **ASCII**:字符编码标准,Unicode包含更多字符,ASCII是其子集。 9. **true** 和 **false**:布尔值,在条件判断和逻辑运算中使用。 10. **variable** 和 **constant**:变量是可以改变的值,...

    Java规约和汇总.pdf

    《Java规约和汇总》这份资料涵盖了这些方面的内容,让我们深入探讨其中的关键知识点。 首先,日志编码的最佳实践是确保其高效且可读。例如,使用`log.isLoggable()`进行条件判断,避免不必要的计算。在给定的例子中...

    2014 最新Java面试题总汇

    本文将对2014年最新Java面试题总汇中的内容进行详细知识点解析。 首先,Java面向对象的核心概念,包括super()与this()的区别,作用域的public、protected、private,以及不写时的区别。super()用于调用父类的构造...

    java文件JSP文件乱码汇总

    **问题描述**:在项目开发中,由于Java默认使用UTF-8编码,但在实际操作中,如使用Struts框架时,往往需要选择UTF-8作为默认编码来避免乱码问题。此外,在使用Eclipse等IDE进行编辑时,应确保default text editor...

Global site tag (gtag.js) - Google Analytics