Java的 I/O主要包括两个方面:I/O Stream和File I/O。前者主要涉及Java的各种流式输入输出,这是Java简化输入输出的一种抽象概念;后者主要涉及文件操作和文件系统。
I/O Stream部分,对应于java.io包,而 File I/O部分对应于 java.nio.file包。下面,分别对这几部分分别进行总结。这让我不禁想起了曾经腾讯的一次面试经历,“请说说Java io和nio的区别”。当时什么不知道什么意思。现在看来,问的都是些很基本的知识,无奈,自己的知识体系不够细腻。问题虽小,但是从侧面可以看出腾讯的技术人员对技术真是细致入微。
I/O Streams
I/O Stream的分类:
按方向分:输入流(读)和输出流(写) 按流处理的数据分:字节流、字符流、缓冲流、数据流、对象流。
注意: 不论是什么流,其实流里面的数据都是二进制串,对于字节流,我们是以8位为单位,进行处理,对于字符流,我们是以16位为单位处理。也就是,不同的流只是处理方式不同而已,本质都是二进制数据。另外,字节流是其他流的基础。
下面,分别对各种输入输出流进行详细的总结和介绍。
字节流
是处理二进制原始数据的I/O流。所有的字节流均继承自InputStream和OutputStream。
下面以FileInputStream()和FileOutputStream()为例,演示字节流的使用。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyBytes {
public static void main(String[] args) throws IOException {
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream("xanadu.txt");
out = new FileOutputStream("outagain.txt");
int c;
while ((c = in.read()) != -1) {
out.write(c);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
其他的字节流的使用和FileInputStream类似,只是构造方法不一样
在使用完之后一定要记得关闭流,关闭流的顺序是打开的顺序是相反的.
字符流
是处理字符原始数据的I/O流,并且能够自动的根据本地字符集进行转换,Java平台默认采用Unicode 存储字符。所有的字符流均继承自Reader和Writer
下面使用FileReader和FileWriter作为例子: import java.io.FileReader; import java.io.FileWriter; import java.io.IOException;
public class CopyCharacters {
public static void main(String[] args) throws IOException {
FileReader inputStream = null;
FileWriter outputStream = null;
try {
inputStream = new FileReader("xanadu.txt");
outputStream = new FileWriter("characteroutput.txt");
int c;
while ((c = inputStream.read()) != -1) {
outputStream.write(c);
}
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
}
}
可以看到,和字节流的操作很类似,都是用一个int值来返回结果,但是值得注意的是,字节流使用的int的低8位,而字符流使用的int的低16位
另外,我们可以使用字节流来初始化一个字符流,实现字节流到字符流的转换
缓冲流
对于非缓冲流,每一次读写都要调用底层操作系统的读写接口进行操作,这样会降低程序的效率.缓冲流正是通过减少调用 底层native API的次数来优化I/O,从而减少不必要的开销。具体是这样的,缓冲输入流每次从内存缓冲区里读取数据,只有缓冲区为空的时候才调用系统API读数据到缓冲区.缓冲输出流则相反,每次写数据到内存缓冲区,只有缓冲区满的时候才调用系统API写数据.
针对不同的输入输出流,Java提供了有四种缓冲流: BufferedInputStream 和BufferedOutputStream(字节缓冲流), BufferedReader 和BufferedWriter(字符缓冲流)
使用方法很简单,只需要用对应的流初始化相应的缓冲流即可实现非缓冲刘和缓冲流的转换。
Java为每一个流都提供了一个flush方法,用于清空缓冲区里的内容。但是,只对缓冲流起作用。对于一些流,带有自动flush功能,当某些操作被触发是自动清理缓冲区。我们可以在构造缓冲流时,指定aotoflush为 true。每当我们需要清理缓冲区或者关闭缓冲流之前,一定要手动调用flush()方法。
数据流 DataStream处理二进制表示的基本数据类型以及String类型的I/O;数据流要么实现 DataInput或者DataOutput接口,两种常用的实现是DataInputStream和DataOutputStream。 数据流只能作为字节流的包装器创建 DataInputStream抛出一个文件结束异常,来表示读结束
下面看一个例子:
static final String dataFile = "invoicedata";
static final double[] prices = { 19.99, 9.99, 15.99, 3.99, 4.99 };
static final int[] units = { 12, 8, 13, 29, 50 };
static final String[] descs = {
"Java T-shirt",
"Java Mug",
"Duke Juggling Dolls",
"Java Pin",
"Java Key Chain"
};
DataOutputStream out = new DataOutputStream(new BufferedOutputStream(
new FileOutputStream(dataFile)));
for (int i = 0; i < prices.length; i ++) {
out.writeDouble(prices[i]);
out.writeInt(units[i]);
out.writeUTF(descs[i]);
}
DataInputStream in = new DataInputStream(new
BufferedInputStream(new FileInputStream(dataFile)));
double price;
int unit;
String desc;
double total = 0.0;
try {
while (true) {
price = in.readDouble();
unit = in.readInt();
desc = in.readUTF();
System.out.format("You ordered %d" + " units of %s at $%.2f%n",
unit, desc, price);
total += unit * price;
}
} catch (EOFException e) {
}
可以看到,使用也很简单
对象流
ObjectStream用来处理二进制表示的对象类型的I/O。 所有的对象流都继承自 ObjectInputStream 和ObjectOutputStream.这两个类分别实现了 ObjectInput 和 ObjectOutput接口,这两个接口有分别继承自 DataInput或者DataOutput接口。 所以,对象流可以同时操作对象和数值。
如果将一个对象重复写入同一个流中,其实只写入了一个,如果将同一个对象写入不同的流中,将得到不同的对象。
File I/O
这一块的内容比较基础,有兴趣的可以参考Java开发者文档。