最近使用Java编写一些读取文件的小工具的时候,经常与IO流打交道,但是自己对IO流的理解不是特别深刻,因此趁着周末,温故一下IO相关的原理和操作。
这里我想先说明一下Java IO流和编码解码的总体关系,知道总体再来细讲局部,理解起来会比较方便。强烈推荐本文最后边参考资料第一个引用,一个知乎作者的回答,链接:https://www.zhihu.com/question/39262026/answer/127103286
一. Java的IO流和编码解码的关系
我们知道,Java在内存中,都是以两个字节的Unicode编码来存储文字的(不管是中文还是英文或其他文字),但是,程序写入到文件中就有多种格式了,如中文可以选择占两个字节的GB2312编码,也可以选择占三个字节的UTF-8编码(英文字符UTF-8编码占一个字节)。如下图:
从硬盘中的文件转为内存中的字节,叫解码;从内存中的字节持久化到硬盘中,叫编码。
文件编码解码
步骤一:文件1是以UTF-8方式编码的,所以需要对它以UTF-8解码,变成内存中的Unicode字符。如果这时以GB2312等其他方式解码,则会产生乱码。
步骤二:内存中的Unicode字符,可以将其编码为你需要的格式存入文件,如UTF-8的文件2,或者GB2312的文件3。
步骤四:GB2312编码的文件需要以GB2312解码形成内存中的Unicode字符
OK,既然知道上面的编码解码关系,那Java是怎么将文件读入内存,又写入到文件中的呢。答案就是IO流,Java中基本的IO流有:
- 输入
- 字节流:InputStream
- 字符流:Reader
- 输出
- 字节流:OutputStream
- 字符流:Writer
两者之间的转换是InputStreanReader、OutputSteamReader,这两个转换需要传入一个字符集。Java中InputStream是最基本的流,就像上图中文件到内存、内存到文件之间连接的线,而InputStreamReader、OutputStreamWriter则是这个线上面的编码方式,经过转换,Java中就存在了Unicode字符。否则单纯的InputStream读入只是存在了字节,Java并不知道这些字节的含义。如果我们做一个单纯的文件复制的功能,则只需要使用InputStream、OutputStream读入写出即可,因为我们不关心内部的文本内容。转换成Reader后,则可以使用BufferReader的高级功能处理行数据。如下图,InputStreamReader构造器传入IpnutStream类和字符集Charset类,内部通过StreamDecoder进行解码。
Java字符集二. Java IO流
基本的文件IO
程序员对IO流应该都不陌生,Java把不同的输入/输出源(键盘,文件,网络连接)等抽象为“流”。Java通过流的形式来访问不同的输入和输出源。一般的Java读取文件并做处理的代码如下:架设InputStream字节管道,使用InputStreamReader将字节解析为字符Reader,内存中现在保存了Unicode编码,使用BufferedReader处理行。
@Test
public void contextLoads() {
// 指定文件
File f = new File("C:\\Users\\huang\\Desktop\\io.txt");
try{
// 架设管道,读入字节流
InputStream is = new FileInputStream(f);
// 字节流解码为unicode
Reader reader = new InputStreamReader(is, "gb2312");
// 使用BufferedReader方便读取一行
BufferedReader br = new BufferedReader(reader);
while (true){
String line = br.readLine();
if(line == null){
break;
}
System.out.println(line);
}
}catch (Exception e){
e.printStackTrace();
}
}
InputStreamReader源码如下:
InputStreamReader
解码是由StreamDecoder处理的,如果不传字符集,默认查找环境变量file.encoding。否则默认UTF-8
默认编码
二. Java编码解码
内存String编码
java中的char类型用来保存一个unicode字符,故它的长度是两个字节。String str = "我爱中国,I love China";在内存中是Unicode编码的,如果要把他保存到文件中,就需要指定编码方式。以下是输入这些文字的Unicode值
@Test
public void contextLoads() {
String str = "我爱中国 China";
for(int i = 0; i < str.length(); i++){
char c = str.charAt(i);
System.out.println(c + " " + Integer.toHexString((int)c));
}
}
运行结果如下,中的unicode编码值为4e2d,国的为56fd,如果使用System.out.println("\u4e2d\u56fd");可以输出中国两字。
Unicode编码
如下图,而如果指定编码,则gb2312和utf-8长度是不一致的。
utf-8编码后如下:
@Test
public void contextLoads() throws UnsupportedEncodingException {
String str = "我爱中国 China";
for(int i = 0; i < str.length(); i++){
char c = str.charAt(i);
byte[] bytes = (c + "").getBytes("utf-8");
String tmp = "";
for(byte b : bytes){
tmp += Integer.toHexString(b & 0x00ff) + " ";
}
System.out.println(c + " " + tmp);
}
}
输出结果:
uft-8编码
gb2312编码,结果如下:
gb2312编码
以上,本篇完结。即将写另外一篇关于IO包源码的文章。
参考资料:
字节字符:Java 中字节流与字符流的区别? - 胖君的回答 - 知乎
https://www.zhihu.com/question/39262026/answer/127103286
IO流:https://blog.csdn.net/lmb55/article/details/79511007
编码解码:https://blog.csdn.net/mazhimazh/article/details/19327421
阮一峰老师关于编码解码的文章:http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
网友评论