IO是各种应用中最常见不过的操作了,无论是键盘输入,文件读写,还是网络访问,加密解密,都少不了对各种数据流读入送出(内存),java.io包中提供了不少定义和操作IO的工具,今天就大致看下:
java.io包内泾渭分明,很容易发现大量InputStream,OutputStream,Reader,Writer.这四大基本抽象类的子类涵盖了io包中大部分类,因为java的io包体系按照字节流和字符流,输入流和输出流分成了4大类别,也就有了4个基本抽象类。顾名思义,字节流操作的粒度是一个字节byte ,字符流操作的粒度是一个字符char,2个byte,而java内部表示字符用的是UTF16-BE编码,2个byte。而输入输出是站在内存的角度上的,进入内存便是输入,从内存送出便是输出。
如此便有这样的基本分类:输入字节流InputStream,输出字节流OutputStream,输入(读)字符流Reader,输出(写)字符流Writer
IO最常见的操作便是文件,那么先用字节流FileInputStream读取一个文件,并输出到控制台:
private static void testFileInputStream() {
//这里先不初始化流,仅仅声明,这是流的一般声明方式,因为资源必须关闭
//所以不能放在try块内
FileInputStream fis = null;
int by=0;
//因为访问文件会涉及文件系统交涉,包括此路径文件存在与否,
//是否是文件夹,是否可读,是否有权限,是否有其他程序正在使用
//状况多多,必须做异常处理
try {
/* 随便指定了一个已经存在的文件,文件内容如下
*
-------- HAXM release 1.1 --------
This log collects running status of HAXM driver.
*/
fis = new FileInputStream("C:\\HaxLogs.txt");
while((by=fis.read())!=-1){
//因为取出的只有一个byte,只好强转成char类型以便显示
System.out.print((char)by);
}//控制台正常输出了文件内容
} catch (IOException e) {
throw new RuntimeException("Read Exception @"
+ fis.toString() + e.getMessage());
} finally {
if (fis != null && fis != System.in) {
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException("Close Exception @"
+ fis.toString() + e.getMessage());
}
}
}
}
看来很顺利的读取了文件
其实这里有一个很大的问题,刚才用的文件是全英文的,1个byte足以表示一个字符,那么中文呢?
换了一个随便写的中文文件:结果如何?
?ò??·????????á???°??????????????°ü?¨???·????????????·???
??·?????????????·?????????·????¨??????·??????????ò????????
×????à?à??±???×??ì?????í
输出是这样的东西。。。。因为汉字的2个byte被切断了,为了能正常读取显示,这里就应该改用字符流了:
字符流最大的特点是字符流是带有字符集编码的,默认编码是操作系统指定的,所以无论是中文还是其他文字,都能正确读写,但是没有字体可是显示不出来咯。
这里还是读取刚才那个文件:
private static void testFileReader() {
FileReader fis = null;
int by=0;
//因为访问文件会涉及文件系统交涉,包括此路径文件存在与否,
//是否是文件夹,是否可读,是否有权限,是否有其他程序正在使用
//状况多多,必须做异常处理
try {
//改用FileReader
fis = new FileReader("S:\\新建文本文档.txt");
while((by=fis.read())!=-1){
//因为by是int类型,只好强转成char类型以便显示
System.out.print((char)by);
}//控制台正常输出了文件内容
} catch (IOException e) {
throw new RuntimeException("Read Exception @"
+ fis.toString() + e.getMessage());
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException("Close Exception @"
+ fis.toString() + e.getMessage());
}
}
}
}
结果输出:
//因为访问文件会涉及文件系统交涉,包括此路径文件存在与否,
//是否是文件夹,是否可读,是否有权限,是否有其他程序正在使用
//状况多多,必须做异常处理
和刚才的做法简直一模一样,还省了异步:除了把FileInputStream换为FileReader就轻松完成读取了。
但是可以发现读取过程中一次仅读取了一个字符,然后循环,这样的做法似乎太没有效率。
查阅Reader类的javadoc read方法还有重载形式read(char[] cbuf),那么就改改:
fis = new FileReader("S:\\新建文本文档.txt");
char[] buf=new char[1024];
while((by=fis.read(buf))!=-1){
System.out.print(new String(buf,0,by));
}//控制台正常输出了文件内容
似乎还不错,其实还可以更进一步,而ava.io已经提供了BufferedReader来提供缓冲,方法很简单,直接包装你需要的Reader流即可:
BufferedReader br=null;
int by=0;
try {
//改用BufferedReader提供缓冲区
br = new BufferedReader(new FileReader("S:\\新建文本文档.txt"));
char[] buf=new char[1024];
while((by=br.read(buf))!=-1){
//因为取出的只有一个byte,只好强转成char类型以便显示
System.out.print(new String(buf,0,by));
}//控制台正常输出了文件内容
}
这样操作读写效率就要高不少了,实际缓冲区和数组块可以按需求调节以达到更好的效果。
值得一提的是BufferedReader还有一个很好用方便的方法readLine(),顾名思义,一次读取一行,操作文本非常好用,特别是配合PrintWriter的println。
有读就有写,写入一个文件类似的就是用Writer和OutputStream,那么这里使用缓冲字节流复制一个文件:
/**
* 尝试拷贝文件,无法拷贝会引发异常退出
*
* @param source
* 源文件
* @param saveFile
* 目标文件
*/
public static void copyFile(File source, File saveFile) {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
// 创建输入流和输出流。
bis = new BufferedInputStream(new FileInputStream(source));
bos = new BufferedOutputStream(new FileOutputStream(saveFile));
// 复制文件
int by;
while ((by = bis.read()) != -1) {
bos.write(by);
}
} catch (IOException e) {
throw new RuntimeException("文件读写失败");
} finally {
// 关闭流
try {
if (bis != null) {
bis.close();
}
} catch (IOException e) {
throw new RuntimeException("输出流关闭失败");
}
try {
if (bos != null) {
bos.close();
}
} catch (IOException e) {
throw new RuntimeException("输出流关闭失败");
}
}
//System.out.println("拷贝完成");
}
至此,所用流操作的都是文件和控制台,而流的应用远不仅于此
Java.io中对于流可以操作的对象总结如下:
流对象 | Java.io所对应的类标识 | 字节流/字符流 |
---|---|---|
控制台 | (System.io/out) | 字节 |
文件 | File* | 均有 |
内存 | ByteArray* | 字节 |
jvm对象 | Object* | 字节 |
jvm原始类型 | Data* | 字节 |
网络 | (通过java.net连接返回) | 字节 |
jvm String | String* | 字符(字节已过时) |
缓冲流已经介绍过了。那么java.io还有很多工具:
转换流可以方便的在字节流和字符流直接转换,需要注意的最重要的问题就是字符集编码。
上面也提过了,PrintWriter有print 和println两个很方便的方法,甚至还有一个format方法支持c语言方式的格式化输出。
(LineNumberInputStream已过时)
行号读取流是缓冲读取流的子类,其特点是int getLineNumber()获得行号方法和void setLineNumber(int lineNumber)设定行号。在文本处理中有时候很有用。
需要注意的是setLineNumber只是修改当前位置的行号,并没有定位到实际的行号位置。
管道流是连接两个流的方法,这个必须成对使用,最好是启动两个线程分别读写。
这个流的特点是可以顺序连接多个输入流,按顺序逐个取,达到多个输入合一的效果。比如用来合并文件。
回退输入流顾名思义可以”回退”,它包装一个输入流,在读取终止字节后,代码片段可以“取消读取”该字节,这样,输入流上的下一个读取操作将会重新读取被推回的字节。
Filter 包装其他的流。
Filter类本身只是简单地重写那些将所有请求传递给所包含流的所有方法,这意味着你可以继承这个流以实现过滤器和修改流的效果。