java I/O体系总结
I/O流的理解
先看看流的概念
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
通俗的说,有两个文件A和B,想要把A的内容拷贝到B中,可以假设两文件间有一个通道,把A的数据按字节或是字符的形式传送给B。这个通道就是java I/O体系流的概念,即可以把流当做抽象的通道。
理解了流的概念,流的分类也就很好明白了。以流的方向来说,流从外界(网络或磁盘)读取到程序(内存)中,就是输入流。流从程序流向外界就是输出流。
基础的流
流又可按传输的数据类型分为字节流和字符流。简单理解就是传输是二进制的字节(字节流)还是直接可以看懂的字符(字符流)。结合输入输出流,java的I/O流基础的类(接口)主要有以下几个:
- InputStream(输入字节流)
- OutputStream(输出字节流)
- Reader(输入字符流)
- Writer(输出字符流)
关于字节流
字节流是最基本的I/O处理流,之所以基本,是因为所有形式的储存(文件,磁盘或网络)底层都是以字节形式存储的。InputStream是所有字节输入流的父类,OutputStream是所有字节输出流的父类。字节流主要用于处理二进制数据。处理单位主要是字节或字节数组。
关于字符流
鉴于有些文件是以文本存储的,为方便操作,于是有了字符流。字符流主要用于处理字符或字符串。每个字符占两个字节。在实现上,字符流即由java虚拟机将字节转为字符(每2个字节转为一个字符)形成的。字符输入流的父类是Writer,字符输出流的父类是Reader。
示例
其他流都是基于以上4个基础接口做的扩展及实现。就以文件传输为例,传输类型为字节,则有FileInputStream(文件输入流)和FileOutputStream(文件输出流)。看下示例:
/**
* 输入流测试
* 将磁盘中的文件读取到内存中,以字节的形式
* 要读取的文件为hello.txt,其中内容为"hello,world"
*/
@Test
public void testInputStream() throws Exception {
InputStream inputStream = null;
try {
//代表磁盘文件
File file = new File("/Users/wangtonghe/local/tmp/hello.txt");
// 构建输入流
inputStream = new FileInputStream(file);
int b = 0;
// 获取到流的内容
while ((b = inputStream.read()) != -1) {
// 输出流的内容
System.out.println((b);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
inputStream.close();
}
}
}
上面这个示例很简单,就是将待操作文件包装到文件输入流中,读取到内存。
看一个整体的示例吧,字符类型的文件输入输出。将a.txt的内容拷贝到b.txt中。FileWriter表示文件字符输出类,FileReader表示文件字符输入类。
一般的做法是,将a.txt包装到文件输入流,读取到内存。再通过文件输出流输出到b.txt文件中。
用图表述即为
@Test
public void testFile() throws Exception {
File aFile = new File("/Users/wangtonghe/local/tmp/a.txt");
File bFile = new File("/Users/wangtonghe/local/tmp/b.txt");
FileReader reader = null;
FileWriter writer = null;
try {
// 构建文件输入流
reader = new FileReader(aFile);
// 构建文件输出流
writer = new FileWriter(bFile);
char[] chars = new char[1024];
int len = 0;
// 读取输入流
if ((len = reader.read(chars)) != -1) {
//写入输出流
writer.write(chars, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (reader != null) {
reader.close();
}
if (writer != null) {
writer.close();
}
}
}
字节流与字符流的区别
- 操作对象不同,这是最基本的区别。字节流操作字节或字节数组;字符流操作字符或字符串。
- 字节流对终端(文件等)直接进行操作,而字符流使用缓冲区(即先把数据写入缓存区,再对缓存区进行操作)
- 由于2的存在,对字节的操作会直接影响终端(文件)结果,而对字符流的操作最后必须关闭流或强制刷新流才能写入数据,否则只是写到了缓存区,文件等终端不受影响。
- 详见 java 字节流与字符流的区别
两种流的相互转化
InputStreamReader以及OutputStreamWriter 负责两种流的相互转换。(一般是字节流转字符流,没有字符转字节,也不需要)
InputStreamReader可以将字节输入流转为字符输入流;OutputStreamWriter可以将字节输出流转为字符输出流。
转化过程如下:
//文本文件
File aFile = new File("/Users/wangtonghe/local/tmp/a.txt");
// 文件字节流
FileInputStream fileInputStream = new FileInputStream(aFile);
// 字节流转为字符流
Reader reader = new InputStreamReader(fileInputStream);
转化流构造方法及用法如下
- InputStreamReader(InputStream); //通过构造函数初始化,使用的是本系统默认的编码表GBK。
- InputStreamWriter(InputStream,String charSet); //通过该构造函数初始化,可以指定编码表。
- OutputStreamWriter(OutputStream); //通过该构造函数初始化,使用的是本系统默认的编码表GBK。
- OutputStreamwriter(OutputStream,String charSet); //通过该构造函数初始化,可以指定编码表
I/O流概览
java体系I/O流大致类及结构就如上图所示。
简要介绍一下
输入字节流InputStream
- InputStream 是所有的输入字节流的父类,它是一个抽象类。
- ByteArrayInputStream、StringBufferInputStream、FileInputStream 是三种基本的介质流,它们分别从Byte数组、StringBuffer、和本地文件中读取数据。
- ObjectInputStream 和所有FilterInputStream 的子类都是装饰流(装饰器模式的主角)。
同理输出字节流OutputStream与此类似
- OutputStream 是所有的输出字节流的父类,它是一个抽象类。
- ByteArrayOutputStream、FileOutputStream 是两种基本的介质流,它们分别向Byte 数组、和本地文件中写入数据。
- ObjectOutputStream 和所有FilterOutputStream 的子类都是装饰流。
I/O体系的装饰器模式
关于I/O体系,一定要谈的就是其装饰器的设计模式。
装饰器模式
概念 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。它能让我们在扩展类的时候让系统较好的保持灵活性。
这里就不具体介绍装饰器模式了,可以看看Java设计模式12:装饰器模式
java I/O中实现装饰器模式主要由FilterInputStream及其子类(输入流)和FilterOutputStream及其子类完成。
如BufferedInputStream,有缓冲功能的输入流,可修饰FileIntputStream使其拥有缓冲功能。
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File(“/home/user/abc.txt”)));