`

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架构师应具备什么技能? 一、常见模式与工具 学习Java技术体系...

    Java开发经验及技巧集大汇总.pdf

    Java 就是用来做项目的!Java 的主要应用领域就是企业级的项目开发!要想从事企业级的项目开发,你必须...Java 的开发总要经过立项——设计——编码——测试等诸多过程,下面先来介绍一下Java 开发中的这些开发经验。

    java中字符串的操作汇总

    学好java,一定要掌握java字符串的操作方式,这样才能更好的了解java的编码方式。

    JAVA程序员笔试面试题汇总及答案.pdf,这是一份不错的文件

    "Java程序员笔试面试题汇总及答案.pdf知识点总结" 以下是从给定的文件中生成的相关知识点: 一、访问控制符的作用域与区别 * private 成员:缺省的成员,只能在同一类中访问 * protected 成员:可以在同一类中和...

    java面试题目与技巧1

    │ J2EE综合--Struts常见错误的全面汇总.txt │ java程序员面试资料.zip │ JAVA笔试题(上海释锐).pdf │ MIME简介.txt │ SCJP试题详解.pdf │ SQL面试题_心灵深处.htm │ Struts+Hibernate+Spring轻量级J2EE...

    java面试题及技巧4

    │ J2EE综合--Struts常见错误的全面汇总.txt │ java程序员面试资料.zip │ JAVA笔试题(上海释锐).pdf │ MIME简介.txt │ SCJP试题详解.pdf │ SQL面试题_心灵深处.htm │ Struts+Hibernate+Spring轻量级J2EE...

    Java程序设计实战案例教程教学课件汇总完整版电子讲义.pptx

    Java程序设计实战案例教程教学课件汇总完整版电子讲义 本讲义总结了Java程序设计的实战案例教程,涵盖了Java平台安装、JDK的安装、Eclipse的安装与使用、第一个Java程序的编写等内容。 一、Java概述 Java是由SUN...

    JAVA入门1.2.3:一个老鸟的JAVA学习心得 PART1(共3个)

    Java编程老鸟潜心写作,奉献高效率的Java学习心得 完全站在没有编程经验读者的角度,手把手教会读者学习Java 配16小时多媒体教学视频,高效、直观 一一击破Java入门可能会遇到的难点和疑惑 抽丝剥茧,层层推进,让...

    java面试题以及技巧

    │ J2EE综合--Struts常见错误的全面汇总.txt │ java程序员面试资料.zip │ JAVA笔试题(上海释锐).pdf │ MIME简介.txt │ SCJP试题详解.pdf │ SQL面试题_心灵深处.htm │ Struts+Hibernate+Spring轻量级J2EE...

    java开源订销管理系统

    以国内开源Web MVC框架EasyJWeb作系统引擎的Java Web应用系统,主要实现的功能有订单录入、打印、销售汇总、原料管理、客户管理、生产配料计算、报表打印、汇总、系统数据管理及维护等功能,是一个使用非常简单的...

    Java Web订销管理系统(java+mysql+html+运行指导说明文档)

    该系统是一个使用Java语言开发,系统主要实现的功能有订单录入、打印,销售汇总、原料管理、客户管理、生产配料计算、报表打印、汇总、系统数据管理及维护等功能,是一个使用非常简单的编码方式实现的Web开源应用系统...

    JAVA实战项目源码-计算机毕业设计java专业-java-源代码-简易java框架开源订销管理系统-信息化办公

    系统主要实现的功能有订单录入、打印,销售汇总、原料管理、客户管理、生产配料计算、报表打印、汇总、系统数据管理及维护等功能,是一个使用非常简单的编码方式实现的Web开源应用系统。 系统采用面向对象的设计...

    JAVA程序员笔试面试题汇总及答案.pdf

    JAVA 程序员笔试面试题汇总及答案 本资源摘要信息涵盖了 JAVA 程序员笔试面试题汇总及答案,涵盖了基础题、中等题、提高题等多种题型,涉及到访问控制符、ArrayList 和 Vector 的区别、HashMap 和 Hashtable 的区别...

    简易java框架开源订销管理系统

    系统主要实现的功能有订单录入、打印,销售汇总、原料管理、客户管理、生产配料计算、报表打印、汇总、系统数据管理及维护等功能,是一个使用非常简单的编码方式实现的Web开源应用系统。 系统采用面向对象的设计...

    java web 开发乱码汇总

    其实, Java 的文件存储/编译机制, JVM 的工作机制, 在实现中内部都采用了统一的 unicode 编码方式, 若目标平台解码方式不一致, 便会出现中文乱码(实际上不仅是中文, 多/单字符集都有可能出问题, 视目标平台而不同).

    Java入门1·2·3:一个老鸟的Java学习心得.PART3(共3个)

    Java编程老鸟潜心写作,奉献高效率的Java学习心得 完全站在没有编程经验读者的角度,手把手教会读者学习Java 配16小时多媒体教学视频,高效、直观 一一击破Java入门可能会遇到的难点和疑惑 抽丝剥茧,层层推进,让...

    毕设-简易java开源订销管理系统

    系统主要实现的功能有订单录入、打印,销售汇总、原料管理、客户管理、生产配料计算、报表打印、汇总、系统数据管理及维护等功能,是一个使用非常简单的编码方式实现的Web开源应用系统。 系统采用面向对象的设计...

    Java编程语言的基础知识作了一个较为全面的汇总-供大家学习研究参考

    错误: 编码GBK的不可映射字符 重要说明:关于数据类型的默认值问题 数据类型划分——浮点形 传统bug问题 数据类型划分——字符型 数据类型划分——布尔型 数据类型划分——字符串型 9、运算符 自增、自减操作 三目...

    信息办公简易java开源订销管理系统-javainfo.zip

    系统主要实现的功能有订单录入、打印,销售汇总、原料管理、客户管理、生产配料计算、报表打印、汇总、系统数据管理及维护等功能,是一个使用非常简单的编码方式实现的Web开源应用系统。 系统采用面向对象的设计...

Global site tag (gtag.js) - Google Analytics