I/O系列教材 (六)- Java 编码中文问题系统透彻讲解 UNICODE GBK UTF-8 ISO-8859-1 之间的区别


步骤1:编码概念

步骤2:常见编码

步骤3:UNICODE和UTF

步骤4:Java采用的是Unicode

步骤5:一个汉字使用不同编码方式的表现

步骤6:文件的编码方式-记事本

步骤7:文件的编码方式-eclipse

步骤8:用FileInputStream 字节流正确读取中文

步骤9:用FileReader 字符流正确读取中文

步骤10:练习-数字对应的中文

步骤11:答案-数字对应的中文

步骤12:练习-移除BOM

步骤13:答案-移除BOM

步骤 1 : 编码概念

计算机存放数据只能存放数字,所有的字符都会被换为不同的数字。

就像一个棋盘一样,不同的字,处于不同的位置,而不同的位置,有不同的数字编号。

有的棋盘很小,只能放数字和英文

有的大一点,还能放中文

有的“足够”大,能够放下世界人民所使用的所有文字和符号


如图所示,英文字符 A 能够放在所有的棋盘里,而且位置都差不多

中文字符, 中文字符  能够放在后两种棋盘里,并且位置不一样,而且在小的那个棋盘里,就放不下中文


步骤 2 : 常见编码

工作后经常接触的编码方式有如下几种:

ISO-8859-1 ASCII 数字和西欧字母

GBK GB2312 BIG5 中文

UNICODE (统一码,万国码)


其中

ISO-8859-1 包含 ASCII

GB2312 是简体中文,BIG5是繁体中文,GBK同时包含简体和繁体以及日文。

UNICODE 包括了所有的文字,无论中文,英文,藏文,法文,世界所有的文字都包含其中

步骤 3 : UNICODE和UTF

根据前面的学习,我们了解到不同的编码方式对应不同的棋盘,而UNICODE因为要存放所有的数据,那么它的棋盘是最大的。

不仅如此,棋盘里每个数字都是很长的(4个字节),因为不仅要表示字母,还要表示汉字等。


如果完全按照UNICODE的方式来存储数据,就会有很大的浪费。

比如在ISO-8859-1中,a 字符对应的数字是0x61

而UNICODE中对应的数字是 0x00000061,倘若一篇文章大部分都是英文字母,那么按照UNICODE的方式进行数据保存就会消耗很多空间


在这种情况下,就出现了UNICODE的各种减肥子编码, 比如UTF-8对数字和字母就使用一个字节,而对汉字就使用3个字节,从而达到了减肥还能保证健康的效果


UTF-8,UTF-16和UTF-32 针对不同类型的数据有不同的减肥效果,一般说来UTF-8是比较常用的方式


UTF-8,UTF-16和UTF-32 彼此的区别在此不作赘述,有兴趣的可以参考 ​​unicode-百度百科​


步骤 4 : Java采用的是Unicode

写在.java源代码中的汉字,在执行之后,都会变成JVM中的字符。

而这些中文字符采用的编码方式,都是使用UNICODE. "中"字对应的UNICODE是4E2D,所以在内存中,实际保存的数据就是十六进制的0x4E2D, 也就是十进制的20013。


​package​​​ ​​stream;​


​public​​​ ​​class​​​ ​​TestStream {​

​public​​​ ​​static​​​ ​​void​​​ ​​main(String[] args) {​

​String str = ​​​​"中"​​​​;​

​}​

​}​


步骤 5 : 一个汉字使用不同编码方式的表现

以字符  为例,查看其在不同编码方式下的值是多少


也即在不同的棋盘上的位置



​package​​​ ​​stream;​


​import​​​ ​​java.io.UnsupportedEncodingException;​


​public​​​ ​​class​​​ ​​TestStream {​


​public​​​ ​​static​​​ ​​void​​​ ​​main(String[] args) {​

​String str = ​​​​"中"​​​​;​

​showCode(str);​

​}​


​private​​​ ​​static​​​ ​​void​​​ ​​showCode(String str) {​

​String[] encodes = { ​​​​"BIG5"​​​​, ​​​​"GBK"​​​​, ​​​​"GB2312"​​​​, ​​​​"UTF-8"​​​​, ​​​​"UTF-16"​​​​, ​​​​"UTF-32"​​​ ​​};​

​for​​​ ​​(String encode : encodes) {​

​showCode(str, encode);​

​}​


​}​


​private​​​ ​​static​​​ ​​void​​​ ​​showCode(String str, String encode) {​

​try​​​ ​​{​

​System.out.printf(​​​​"字符: \"%s\" 的在编码方式%s下的十六进制值是%n"​​​​, str, encode);​

​byte​​​​[] bs = str.getBytes(encode);​


​for​​​ ​​(​​​​byte​​​ ​​b : bs) {​

​int​​​ ​​i = b&​​​​0xff​​​​;​

​System.out.print(Integer.toHexString(i) + ​​​​"\t"​​​​);​

​}​

​System.out.println();​

​System.out.println();​

​} ​​​​catch​​​ ​​(UnsupportedEncodingException e) {​

​System.out.printf(​​​​"UnsupportedEncodingException: %s编码方式无法解析字符%s\n"​​​​, encode, str);​

​}​

​}​

​}​


步骤 6 : 文件的编码方式-记事本

接下来讲,字符在文件中的保存

字符保存在文件中肯定也是以数字形式保存的,即对应在不同的棋盘上的不同的数字

记事本打开任意文本文件,并且另存为,就能够在编码这里看到一个下拉。

ANSI 这个不是ASCII的意思,而是采用本地编码的意思。如果你是中文的操作系统,就会使GBK,如果是英文的就会是ISO-8859-1

Unicode UNICODE原生的编码方式

Unicode big endian 另一个 UNICODE编码方式

UTF-8 最常见的UTF-8编码方式,数字和字母用一个字节, 汉字用3个字节。


步骤 7 : 文件的编码方式-eclipse

eclipse也有类似的编码方式,右键任意文本文件,点击最下面的"property"

就可以看到Text file encoding

也有ISO-8859-1,GBK,UTF-8等等选项。

其他的US-ASCII,UTF-16,UTF-16BE,UTF-16LE不常用。


步骤 8 : 用FileInputStream 字节流正确读取中文

为了能够正确的读取中文内容

1. 必须了解文本是以哪种编码方式保存字符的

2. 使用字节流读取了文本后,再使用对应的编码方式去识别这些数字,得到正确的字符

如本例,一个文件中的内容是字符,编码方式是GBK,那么读出来的数据一定是D6D0。

再使用GBK编码方式识别D6D0,就能正确的得到字符


注: 在GBK的棋盘上找到的字后,JVM会自动找到在UNICODE这个棋盘上对应的数字,并且以​​UNICODE上的数字保存在内存中​​。



​package​​​ ​​stream;​


​import​​​ ​​java.io.File;​

​import​​​ ​​java.io.FileInputStream;​

​import​​​ ​​java.io.IOException;​


​public​​​ ​​class​​​ ​​TestStream {​


​public​​​ ​​static​​​ ​​void​​​ ​​main(String[] args) {​

​File f = ​​​​new​​​ ​​File(​​​​"E:\\project\\j2se\\src\\test.txt"​​​​);​

​try​​​ ​​(FileInputStream fis = ​​​​new​​​ ​​FileInputStream(f);) {​

​byte​​​​[] all = ​​​​new​​​ ​​byte​​​​[(​​​​int​​​​) f.length()];​

​fis.read(all);​


​//文件中读出来的数据是​

​System.out.println(​​​​"文件中读出来的数据是:"​​​​);​

​for​​​ ​​(​​​​byte​​​ ​​b : all)​

​{​

​int​​​ ​​i = b&​​​​0x000000ff​​​​;  ​​​​//只取16进制的后两位​

​System.out.println(Integer.toHexString(i));​

​}​

​System.out.println(​​​​"把这个数字,放在GBK的棋盘上去:"​​​​);​

​String str = ​​​​new​​​ ​​String(all,​​​​"GBK"​​​​);​

​System.out.println(str);​

​} ​​​​catch​​​ ​​(IOException e) {​

​// TODO Auto-generated catch block​

​e.printStackTrace();​

​}​


​}​

​}​


步骤 9 : 用FileReader 字符流正确读取中文

FileReader得到的是字符,所以一定是已经把字节根据某种编码识别成了字符

而FileReader使用的编码方式是Charset.defaultCharset()的返回值,如果是中文的操作系统,就是GBK

FileReader是不能手动设置编码方式的,为了使用其他的编码方式,只能使用InputStreamReader来代替,像这样:


​new​​​ ​​InputStreamReader(​​​​new​​​ ​​FileInputStream(f),Charset.forName(​​​​"UTF-8"​​​​));​


在本例中,用记事本另存为UTF-8格式,然后用UTF-8就能识别对应的中文了。


解释: 为什么中字前面有一个?

如果是使用记事本另存为UTF-8的格式,那么在第一个字节有一个标示符,叫做BOM用来标志这个文件是用UTF-8来编码的。



​package​​​ ​​stream;​


​import​​​ ​​java.io.File;​

​import​​​ ​​java.io.FileInputStream;​

​import​​​ ​​java.io.FileNotFoundException;​

​import​​​ ​​java.io.FileReader;​

​import​​​ ​​java.io.IOException;​

​import​​​ ​​java.io.InputStreamReader;​

​import​​​ ​​java.io.UnsupportedEncodingException;​

​import​​​ ​​java.nio.charset.Charset;​


​public​​​ ​​class​​​ ​​TestStream {​


​public​​​ ​​static​​​ ​​void​​​ ​​main(String[] args) ​​​​throws​​​ ​​UnsupportedEncodingException, FileNotFoundException {​

​File f = ​​​​new​​​ ​​File(​​​​"E:\\project\\j2se\\src\\test.txt"​​​​);​

​System.out.println(​​​​"默认编码方式:"​​​​+Charset.defaultCharset());​

​//FileReader得到的是字符,所以一定是已经把字节根据某种编码识别成了字符了​

​//而FileReader使用的编码方式是Charset.defaultCharset()的返回值,如果是中文的操作系统,就是GBK​

​try​​​ ​​(FileReader fr = ​​​​new​​​ ​​FileReader(f)) {​

​char​​​​[] cs = ​​​​new​​​ ​​char​​​​[(​​​​int​​​​) f.length()];​

​fr.read(cs);​

​System.out.printf(​​​​"FileReader会使用默认的编码方式%s,识别出来的字符是:%n"​​​​,Charset.defaultCharset());​

​System.out.println(​​​​new​​​ ​​String(cs));​

​} ​​​​catch​​​ ​​(IOException e) {​

​// TODO Auto-generated catch block​

​e.printStackTrace();​

​}​

​//FileReader是不能手动设置编码方式的,为了使用其他的编码方式,只能使用InputStreamReader来代替​

​//并且使用new InputStreamReader(new FileInputStream(f),Charset.forName("UTF-8")); 这样的形式​

​try​​​ ​​(InputStreamReader isr = ​​​​new​​​ ​​InputStreamReader(​​​​new​​​ ​​FileInputStream(f),Charset.forName(​​​​"UTF-8"​​​​))) {​

​char​​​​[] cs = ​​​​new​​​ ​​char​​​​[(​​​​int​​​​) f.length()];​

​isr.read(cs);​

​System.out.printf(​​​​"InputStreamReader 指定编码方式UTF-8,识别出来的字符是:%n"​​​​);​

​System.out.println(​​​​new​​​ ​​String(cs));​

​} ​​​​catch​​​ ​​(IOException e) {​

​// TODO Auto-generated catch block​

​e.printStackTrace();​

​}​


​}​

​}​


更多内容及答案,点击了解: ​http://how2j.cn/k/io/io-encoding/695.html?p=105008">​​http://how2j.cn/k/io/io-encoding/695.html​​​