前言:以下分析只针对纯文本
1.FileInputStream默认的编码方式就是文件的编码方式
即:源文件是什么编码方式,则利用FileInputStream默认读取的字节数组,就是什么编码方式。
例:纯文本采用“GBK”编码,文本内容如下(注意:文本是纯汉字):
你好世界我是潘小白
利用“GBK”字符集解码如下:
package cn.edu.uestc.IO; import java.io.*; public class TestFileInputStream03 { public static void main(String[] args){ //流 File file = new File("abc3.txt"); //源 InputStream is = null; try { is = new FileInputStream(file); //操作 byte[] bytes = new byte[4];//这里数组容量必须采用2的倍数,具体原因后面后谈 int len = -1; while ((len = is.read(bytes))!=-1){ String str = new String(bytes,0,len,"GBK");//利用GBK字符集,对FileInputStream读取的字节数组进行解码 System.out.print(str); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ //释放资源 try { if (null!=is){ is.close(); } } catch (IOException e) { e.printStackTrace(); } } } } /*output: 你好世界我是潘小白 */
分析:通过代码可知,我采用FileInputStream对格式为“GBK”的纯汉字文本读取,得到的字节数组,可以用"GBK"字符集对其完美解码;反推可知,FileInputStream默认读取的字节数组,其编码格式和原文件编码格式相同。接下来,用"UTF-8"对其进行解码试一试。。。
利用“UTF-8”字符集解码如下:
package cn.edu.uestc.IO; import java.io.*; public class TestFileInputStream03 { public static void main(String[] args){ //流 File file = new File("abc3.txt"); //源 InputStream is = null; try { is = new FileInputStream(file); //操作 byte[] bytes = new byte[4];//这里数组容量采用3的倍数,区别于上面GBK解码时2的倍数,具体原因后面谈 int len = -1; while ((len = is.read(bytes))!=-1){ String str = new String(bytes,0,len,"UTF-8");//利用UTF-8字符集对字节数组进行解码 System.out.print(str); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ //释放资源 try { if (null!=is){ is.close(); } } catch (IOException e) { e.printStackTrace(); } } } } /*output: �������������С��//输出无法解码 */
分析:利用UTF-8无法解码,再次说明,FileInputStream默认读取的字节数组的编码格式,就是原文件的编码格式。
同理读者可以将纯文本(纯汉字文本)设置成UTF-8的编码格式,再分别采用“GBK”和“UTF-8”方式解码试一试,特别注意数组容量的选择,即:“纯汉字文本,GBK解码时,字节数组容量是2的倍数”、““纯汉字文本,UTF-8解码时,字节数组容量是3的倍数”,原因下面分析。
————————简单的分割————————
2.采用“GBK”对纯汉字文本解码时,字节数组容量是2的倍数;“UTF-8”对纯汉字文本解码时,字节数组容量是3的倍数。
原因是:“GBK”编码时,一个汉字是2个字节,“UTF-8”对常规汉字编码时,一个汉字是3个字节(UTF-8方式下,生僻汉字也可能会占4个字节,这种方式此处不谈)。
所以,你要对字节数组解码时,你首先必须成组的取字节(“GBK”模式下2的倍数一组,“UTF-8”模式下3的倍数一组),否则会将一个汉字的字节拆开,这样肯定会乱码,其对应着我上一篇文章提到的“字节数不全或者丢失情况,产生的乱码”。
此处,我们用代码做一下简单示范,原文本采用“GBK”编码,字节数组容量采用3,不是2的倍数:
package cn.edu.uestc.IO; import java.io.*; public class TestFileInputStream03 { public static void main(String[] args){ //流 File file = new File("abc3.txt"); //源 InputStream is = null; try { is = new FileInputStream(file); //操作 byte[] bytes = new byte[3];//不是2的倍数 int len = -1; while ((len = is.read(bytes))!=-1){ String str = new String(bytes,0,len,"GBK");//却用GBKJ解码 System.out.print(str); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ //释放资源 try { if (null!=is){ is.close(); } } catch (IOException e) { e.printStackTrace(); } } } } /*output: 你�檬�界�沂�潘�“�//也就第一个字取全了,解码出来,但是后面字节数乱了,也就无法解码了 */
结果看到,基本全部乱码。
同理,读者可以采用UTF-8的文本,而设置字节数组容量不是3的倍数,从而进行UTF-8解码,试试看;你会发现,即使编码-解码的字符集同步,但是字节数组中字节个数不对,同样乱码。
——————简单的分割线——————
上面问题2中,“编码-解码的字符集同步,字节数组中字节个数不匹配出现乱码”可以进一步延伸;
我们看到上面,都是纯汉字文本,没有任何英文字符(包括英文字母和英文标点),如果文本是,中英文混合怎么办,还能否采用上面的方式,对FileInputStream读取的字节进行解码呢??
答案是:不能,见下面分析。
3.中英文混合纯文本,用FileInputStream读取时,得到的字节数组无法采用上面 String str = new String(bytes,0,length,"CharacterSet")方式解码,应该采用字符转换流InputStreamReader。
(提示:这里不再考虑标点符号的事了,你可以将英文标点符号看出一个英文字母,中文下的标点看成一个普通汉字分析,因为同一种编码格式下,中文字母和中文标点占用字节数一样,英文字母和英文标点占用字节数一致)
原因:无论是"GBK"还是"UTF-8",英文占用1个字节,所以,当插入引文时,一定会改变字节个数混乱,无法保证“在GBK格式下,每个汉字的两个字节同时被字节数组读取”,也无法保证“在UTF-8格式下,每个汉字的三个字节同时被字节数组读取”,那么将导致后期解码时,出现乱码。
示例:文本格式是“GBK”,文本中插入了一个英文字母
你好p世界我是潘小白
代码如下:
package cn.edu.uestc.IO; import java.io.*; public class TestFileInputStream03 { public static void main(String[] args){ //流 File file = new File("abc3.txt"); //源 InputStream is = null; try { is = new FileInputStream(file); //操作 byte[] bytes = new byte[2];//字节数组容量采用2 int len = -1; while ((len = is.read(bytes))!=-1){ String str = new String(bytes,0,len,"GBK");//GBK解码,实现编码-解码格式匹配 System.out.print(str); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ //释放资源 try { if (null!=is){ is.close(); } } catch (IOException e) { e.printStackTrace(); } } } } /*output: 你好p�澜缥沂桥诵“� */
结果分析:输出结果从字母p以后,出现乱码;这里选取得字节数组的容量是2,所以前两个汉字被一一读取,并完美解码,但是读取字母p得时候,因为其只占用一个字节,所以汉字“世”被取一个字节,留下一个字节,未被取走,所以导致“世”字无法被正确解码,而且这也引发连锁效应,后面得字都将被错误得读取,从而乱码。
总之:这也是乱码得一种情况,即“字节数丢失或者不完整造成乱码”。
这里,可能有人会有疑问,“如果将字节数组容量设置非常大,一次将中英文混合文本全部读取,然后再解码,这样不出现文字多次读取,造成汉字字节截断得情况,不就行了吗?”
是的,这种情况可以实现正确解码,但是如果文本超级大,这种方式是不现实得,因为字节数组得容量过大,不现实,还是乖乖的用字符转换流InputStreamReader吧。
下面用一个超大字节数组,将文本一次读取,并完美解码得代码示例:
package cn.edu.uestc.IO; import java.io.*; public class TestFileInputStream03 { public static void main(String[] args){ //流 File file = new File("abc3.txt"); //源 InputStream is = null; try { is = new FileInputStream(file); //操作 byte[] bytes = new byte[20];//数组容量超级大,一次能将中英混合文本全部读取完 int len = -1; while ((len = is.read(bytes))!=-1){ String str = new String(bytes,0,len,"GBK"); System.out.print(str); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ //释放资源 try { if (null!=is){ is.close(); } } catch (IOException e) { e.printStackTrace(); } } } } /*output: 你好p世界我是潘小白//完美解码 */
除了上面的情况,读者也可以试试,对于中英文结合的文档,采用UTF-8编码-解码;或者故意将英文字体排布规则,即GBK格式下,2个英文一起排列,放在中文文本中,或者GBK格式下,2个英文一起排列,放在中文文本中。对其进行编码和解码,并分析一下原因。
补充一点:UTF-8编码格式下,一些生僻汉字占4个字节,所以将字节数组容量设置成3的倍数时,面对有生僻字的纯汉字文本,解码时也会出现乱码情况。
——————分割线——————
总结:
上面讨论的三个问题,问题1就是属于编码-解码字符集匹配问题,只是进一步说明了FileInputStream读取的字节数组是哪种编码方式;
问题2和3,是讨论在编码-解码字符集匹配情况下,字节个数不完整或者丢失时,解码时出现乱码的情况,从而说明了用FileInputStream读取时,得到的字节数组无法采用上面 String str = new String(bytes,0,length,"CharacterSet")方式解码,应该采用字符转换流InputStreamReader。