前面说到,文本文件中没有编码信息,致使了各类混乱,那么,最关键的就是要指定好所用的编码信息。具体地讲,有如下一些途径。css
什么是变相引入呢?其实本质与前面提到的一些“文件头”信息是相似的。html
xmljava
咱们来看看xml文件的例子,你一般能在最开始看到这样的一行:python
<?xml version="1.0" encoding="UTF-8"?>
那么这里面,encoding指明的就是所用编码的信息了。正则表达式
但是,等等!!为了获得这一编码信息,我得先读取这一文件;可要正确读取文件,我又要先知道编码信息!apache
这成了一个鸡生蛋,蛋生鸡,又或者说是先有鸡仍是先有蛋的问题了。api
怎么破呢?考虑这一行信息全部字符都是ASCII中的字符,那么咱们能够先使用最基础的ASCII去读取它开头的一些信息,获取到这一编码信息后,再次用这一编码去读取文件便可。ruby
ASCII可谓是这样一个始祖鸟或者始祖蛋同样的存在。jsp
能够动动手作些实验,先创建一个xml文件,好比就叫foo.xmlmaven
内容以下:
<?xml version="1.0" encoding="UTF-8"?> <foo>向我开炮</foo>
而后初步测试读取编码信息
package org.jcc.core.encode; import static org.assertj.core.api.Assertions.assertThat; import java.io.File; import java.nio.charset.StandardCharsets; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.io.FileUtils; import org.junit.Test; public class EncodingDetectTest { @Test public void testEncodingDetect() throws Exception { File foo = FileUtils.toFile(getClass().getResource("/foo.xml")); // 以ASCII方式读取文件 String content = FileUtils.readFileToString(foo, StandardCharsets.US_ASCII); // 匹配到首行,并用group方式抓取编码的值 Pattern headerPattern = Pattern.compile("<\\?xml[\\s\\S]*encoding=\"([^\"]*)\"\\?>"); Matcher headerMatcher = headerPattern.matcher(content); assertThat(headerMatcher.find()).isTrue(); assertThat(headerMatcher.group(1)).isEqualTo("UTF-8"); // 匹配foo节点中的内容“向我开炮” Pattern fooPattern = Pattern.compile("<foo>([\\s\\S]*)</foo>"); Matcher fooMatcher = fooPattern.matcher(content); assertThat(fooMatcher.find()).isTrue(); // 四个UTF-8字符,每一个三字节,共12字节. // 因为最高位都为1,都不是有效的ASCII字节,最终被替换成了12个�(见乱码探源2中的介绍) assertThat(fooMatcher.group(1)).isEqualTo("������������"); } }
注:仅为演示用,就写得比较粗糙了。好比直接就把所有内容读取上来了,精细一点应该是读取一行或者读取到所需信息就好了。正则表达式也还能够写得更严谨些。
以后就能够进一步测试了:
@Test public void testRereadUsingDetectedEncoding() throws Exception { File foo = FileUtils.toFile(getClass().getResource("/foo.xml")); // 获取xml中的编码信息 String encoding = getXmlEncoding(foo); // 使用检测到的编码再次读取文件 String content = FileUtils.readFileToString(foo, encoding); // 此次,内容正确了。 assertThat(getTextInFooNode(content)).isEqualTo("向我开炮"); } private String getXmlEncoding(File foo) throws Exception { String content = FileUtils.readFileToString(foo, StandardCharsets.US_ASCII); Pattern headerPattern = Pattern.compile("<\\?xml[\\s\\S]*encoding=\"([^\"]*)\"\\?>"); Matcher headerMatcher = headerPattern.matcher(content); headerMatcher.find(); return headerMatcher.group(1); } private String getTextInFooNode(String content) { Pattern fooPattern = Pattern.compile("<foo>([\\s\\S]*)</foo>"); Matcher fooMatcher = fooPattern.matcher(content); fooMatcher.find(); return fooMatcher.group(1); }
此次内容正常了,代表咱们的策略是可行的。
像Html文件也经常会这样去引入一些编码的信息,如在header里常会包括如下元信息:
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
又或者是像这样:
<meta charset="UTF-8">
智能一点的文本编辑器还会根据这一信息来做为保存时的编码。好比在Eclipse中,若是设定了用ISO-8859-1编码,又同时录入了中文,还会出现保存时的警告:
天然,像记事本这样傻乎乎的编辑器就没有这么贴心了。这时,保持宣称编码与实际保存用的编码一致就是源文件做者的责任了,不然可能不但没有帮助还会误导编辑器。
像ruby,python之类的语言有时会在文件头加上以下声明:
# -*- coding: utf-8 -*-
那么,这也算是变相引入的编码信息。天然,这须要相应的源码编辑器及编译(解释)器的支持。
可是,像java这样的语言彷佛没有这样的约定,那么要怎样才能尽量避免出错呢?
既然没有编码信息,又不打算用变相的方式指定,那么靠谱的方式就是外部显式指定了。
假如咱们用UTF-8编码一个java源文件:
而后在cmd下用javac命令手动编译并执行:
咱们发现乱码了,没有输出“你好”,而是三个怪字。缘由实际上就是javac编译器用了缺省的编码,在Windows平台,也就是GBK去读取源文件。
“你好”两个字按UTF-8一个字三个字节,总共6个字节,而按GBK去解码,则两字节一个字,最终成为三个字。
注:也即生成的class文件就已是有缺陷的了。
纠正的方法也很简单,就是在编译时显式指定所用的编码:
javac -encoding utf-8 Foo.java
在加了“encoding”参数后再编译,就能正确的读取源文件从而生成正确的class文件,
注:若是你观察一下新生成的class文件,会比原来的小3个字节。这与class文件中所使用的“modified UTF-8”编码方式有关,可参考前面乱码探源1中的介绍。
再次执行,就正常了:
每次编译时都要去指定这一编码是件很繁琐的事,一般是对一整个工程在一开始就设置一个明确的编码。
好比对于一个Eclipse下的工程,咱们能够在工程属性里指定一个编码,好比用UTF-8:
这样以后,新建的文本文件如各类源代码文件都会使用这一编码。而当要编译时,也会使用这一编码去读取源文件。
固然,若是咱们从外部引入一些文件,编码是不会自动转换的。
好比引入一些css文件,话说天下css一大抄,你多是从某网站直接抓取来的,而不少网站因为历史等缘由可能仍是用GBK等编码。
这时你须要手工转换一下编码,或者用一些批量转换的工具(若是数量不少的话)
手工转的话,好比能够在记事本中先正确打开它,再拷贝到工程中的一个新建文件再保存。
注:编辑时,内容在内存中都是转换成了统一的编码(在Windows下,就是UTF-16),因此不一样编码的文件间互相拷贝也是OK的,只是保存时才再次转换成相应的编码。
也能够在构建文件中指定源文件的编码,好比java中用maven时能够这样指定:
<project> // ... <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> // ... </project>
若是你用的是ant或者gradle之类的,也可自行查查文档要如何设置。至于其它语言平台的构建平台,如grunt,make之类,读者可自行去了解。
总而言之,越是明确地设置了编码,才越能避免混乱的出现。
在下一篇,咱们再谈下在内存中的编码及相关的string,字节流及字符流的话题。