经常听大家谈论"java使用的是Unicode编码",真不知道他们是真知道,还人云亦云 !
首先说一下,java中class文件中使用的是utf-8编码,而在jvm运行时使用的是utf-16(如char)。
一、字节的形式写、读取文件
// 以字节流的形式写出 FileOutputStream out = new FileOutputStream("text.txt"); out.write("IamChinese".getBytes("utf-8")); out.write("我是中国人".getBytes("utf-8")); out.close(); // 以字节流的形式读出 FileInputStream in = new FileInputStream("text.txt"); byte[] array = new byte[25]; in.read(array); System.out.println(new String(array, "utf-8")); in.close();
这里无论是 "IamChinese" 抑或 "我是中国人" ,暂时不管它在java中使用unicode编码时是使用什么字节表示的,我们只要关注逻辑上(到哪里他们都是一样字面值)它们是字符串,而这时我们想把这些字符串对应的utf-8编码后的字节流保存到文件中,所以在读取时我们可以按照保存的编码格式再反过来得到字符串字面值(因为英文字符使用utf-8编码时占用一个字节,而一个中文编码时占用3个字节,所有这里使用了长度为25的byte数组来保存从文件中读取的字节)。到这里时,你是不是认为这时的新new出的那个String它内部存放的是字符都是utf-8的格式呢,事实不然,已经强调过了,java中采用的是unicode编码,所以在new的时候,方法内部默默地使得存储的是unicode字符。
二、字符的形式写、读取文件
// 以字符流的形式写出 BufferedWriter out = new BufferedWriter(new OutputStreamWriter( new FileOutputStream("text.txt"), "utf-8")); out.write("IamChinese"); out.write("我是中国人"); out.close(); // 以字符流的形式逐个读出 BufferedReader in = new BufferedReader(new InputStreamReader( new FileInputStream("text.txt"), "utf-8")); int c; int i = 0; while ((c = in.read()) != -1) { System.out.println((char) c); System.out.println(c); i++; } System.out.println("一共有 " + i + " 个字符"); in.close();
上面的代码在写出数据时和用字节流实现的是一样,只不过是使用api直接操作,而省略了自己的一步步构造字节的过程,但是奇怪的事情在于in.read()这条代码(api doc说明指出它是读出一个字符,也就是说不管你字符流是utf-8还是gbk编码,它肯定是返回一个字符),奇怪之处在于它的返回值是一个0-65535的整数,这可把我急坏了,这怎么可能呢?如果文件使用gbk编码,那么读取时使用的2个字节(0-65535)可以存下,但是当使用utf-8时一个中文三个字节啊,它是怎么转换的呢??!!其实并不是这样的,这时的read返回的整数只是这个字符对应的unicode编码时的双字节表示时的整数,并非字符存储时的具体编码对应的字节而后转换的整数。这也同时说明了在read的内部它已经将我们指定的编码格式的字符转换成了unicode编码,而在读取不同编码的文本文件时,只要我们正确指定编码方案,那么read方法会自己准取地读取一个字符(变现在文件里三个字节(utf-8的中文)或者一个字节(utf-8的英文字母)或者两个字符(gbk编码的中文)),然后将该字符根据unicode码表再翻译成一个2个字节的整数。
三、java中的中文字符编码
char c=‘中‘; System.out.println((int)c); int a=20013; System.out.println((char)a); char cc=‘a‘; System.out.println((int)cc); System.out.println(Arrays.toString("a".getBytes("utf-8")));
四、注意事项
1. 无论什么格式的文件:字节流形式的(jpg、rmvb)或字符格式(txt),当它们被保存到磁盘上时都是字节形式,各种应用程序读取时(无论使用字符流、字节流)都是一个字节一个字节的读取,只是各种流api在使用装饰模式后,可能有了缓冲,封装字符(这时就要用到文件保存时使用的编码格式,如utf-8或者gbk)的操作,提供给用户的直接调用api就由它的名字“望文生义”,从而有了字符流。
2. 一切文件都可以使用字节流的形式读取。但是,对于字符文件,如果硬是使用字节流读取(且还需要解码出字符文字),那么就必须得事先知道该字符文件在保存时使用的是那种编码格式,否者我们得到的只是字节,当使用字节数组转换字符、字符串(如 new String(bytes,"utf-8");)时就会出错(而实际的编码字符编码使用的是gbk),这也是乱码的出现的本质原因。
3. 既然所有的文件都可以使用字节流的形式读取,那为何还有有字符流呢? 提供字符流的形式读取只是为我们方便的读取字符文件,为我们提供直接的读取字符的api(如readLine(),而不是每次都是用户自己读取字节,让后在自己根据编码格式生成字符。
4.什么叫java文本文件编码格式,class文件的编码格式,jvm运行时编码格式?它们之间有什么区别和影响吗?前两者都是文件保存到磁盘上时,将字符写成字节时使用的字符集类型,无论什么歌格式,只要读取时和写入时使用同一种字符集就没问题。而最后一者则是在内存中用以表示字符的字节形式。
5.java中无论什么字符都是两个字节,所以像英文字母这样的字符,在utf-8中只要一个字节,但是在unicode中就要再浪费一个字节存储。
6. 至于在utf-8的文件中,因为编码时针对不同类型的字符占用不同的字节数,字符流是怎么区分的呢?你可以参考utf-8编码规范,自然可以知道。