Java IO 用的是‘decorator模式’来构建的。要容易理解Java IO 应该从对称性和两个模式(装饰器模式和适配器模式)这两方面进行分析。
装饰器模式:
在由InputStream、OutputStream、Reader和Writer 代表的等级结构内部,有一些流处理器可以对另外一些流处理器起到装饰作用,形成新的、具有改善了的功能的流处理器。
下面是Decorator模式的结构图:
1.Component就是装饰器模式中公共方法的类,在装饰器模式结构图的顶层。
2.ConcreateComponent是转换器模式中具体的被装饰的类,IO包中的媒体流就是此种对象。
3.Decorator装饰器模式中的核心对象,所有具体装饰器对象的父类,完成装饰器的部分职能。在上面的例子中Decorator类和这里的对应。该类可以只做一些简单的包裹被装饰的对象,也可以还包含对Component中方法的实现……他有一个鲜明的特点:继承至Component,同时包含一个Component作为其成员变量。装饰器模式动机中的动态地增加功能是在这里实现的。
4.ConcreteDecoratorA和ConcreteDecoratorB是两个具体的装饰器对象,他们完成具体的装饰功能。装饰功能的实现是通过调用被装饰对象对应的方法,加上装饰对象自身的方法。这是装饰器模式动机中的添加额外功能的关键。
装饰器模式相关知识参考http://miaoxiaodong78.blog.163.com/blog/static/18765136200701232434996/
适配器模式:
将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 比如ByteArrayInputStream 是一个适配器类,它继承了InputStream,而封装了一个byte数组,换句话说,它将以个byte数组的接口适配成InputStream流处理器的接口。
适配器的结构图:
适配器相关的知识参考:http://www.cnblogs.com/houleixx/archive/2008/03/04/1090214.html
下面开始了解Java IO
Java IO 中的流从不同的角度可以分成三类:
1、按数据流的方向不同可以分为输入流(InputStream/Reader)和输出流(OutputStream/Writer),用户可以从输入流中读取信息,但不能写它。相反,对输出流,只能往往输出流写,而不能读取。
2、按处理数据单位不同可以分为字节流(InputStream/OutputStream-8位)和字符流(Reader/Writer-16位)。
3、按功能不同可以分为节点流(原始流)和处理流(链接流)。节点流为可以从一个特定的数据源(节点)读写数据(如文件、内存)。处理流是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。
下面的分析是从功能上来分析的:
字节流处理类概述:
对于字节流处理的类都继承自InputStream和OutputStream这两个抽象类。
InputStream
InputStream结构图,从图中可以很清楚的看到装饰器模式的应用。
InputStream提供的最重要的方法是:
- read();
- read(byte[] b) ;
- read(byte[] b, int off, int len) ;
read();
read(byte[] b) ;
read(byte[] b, int off, int len) ;
用于从输入流中读取字节。
原始流(也是一个适配器类):
(1)ByteArrayInputStream:为多线程的通信提供缓冲区操作,接收一个Byte数据作为流的源。
(2)FileInputStream:建立一个与文件有关的输入流。接收一个File对象作为流的源。
(3)PipedInputStream:可以与PipedOutputStream配合使用,用于读入一个数据管道的数据,接收一个PipedOutputStream 作为源。
(4)StringBufferInputStream:将一个字符串缓冲区转换为一个输入流。接收一个String对象作为流的源。
链接流:
(1)FilterInputStream称为过滤输入流,它将另外一个输入流作为流源。这个类的子类包括一下几种。
BufferedInputStream: 为另一个输入流添加一些功能,即缓冲输入以及支持 mark 和 reset 方法的能力。在创建BufferedInputStream 时,会创建一个内部缓冲区数组。在读取或跳过流中的字节时,可根据需要从包含的输入流再次填充该内部缓冲区,一次填充多个字节。mark 操作记录输入流中的某个点,reset 操作使得在从包含的输入流中获取新字节之前,再次读取自最后一次 mark 操作后读取的所有字节。
DataInputStream:数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用数据输出流写入稍后由数据输入流读取的数据。
LineNumberInputStream(已过时):提供带有行计数功能的过滤输入流。
PushbackInputStream:提供特殊的功能,可以将已经读取的字节“推回”到输入流中。
(2)ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化
(3)SequenceInputStream 表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。
从结构图中可以看出直接引用InputStream(相当于装饰器模式中的Component) 的类就是链接流了。而FilterInputStream 相当于装饰器模式中的Decorator。以下的也是一样,所以就不一一分析了,其类的功能可查看API 文档。
OutputStream(和InputStream相对应)
OutputStream结构图:
OutputStream提供的最重要的方法是:
- write(int b);
- write(byte[] b);
- write(byte[] b, int off, int len)
write(int b);
write(byte[] b);
write(byte[] b, int off, int len)
用于将字节写入输出流。
字符流处理概述:
所有的字符流操作类都继承自Reader或者Writer这两个抽象类。
Reader
Reader结构图:
Reader提供的重要方法有:
- read(char[] cbuf);
- read(char[] cbuf, int off, int len);
- read(CharBuffer target);
read(char[] cbuf);
read(char[] cbuf, int off, int len);
read(CharBuffer target);
他们提供了从流中读取数据到字符数组或者CharBuffer的功能。
Writer
Writer结构图:
Writer提供的重要方法有:
- write(char[] cbuf);
- write(char[] cbuf, int off, int len);
- write(int c);
- write(String str);
- write(String str, int off, int len);
write(char[] cbuf);
write(char[] cbuf, int off, int len);
write(int c);
write(String str);
write(String str, int off, int len);
他们提供了把字符、字符数组或者字符串写入流中的功能。
字节流和字符流之间的转换(从字节流适配到字符流)
InputStreamReader和OutputStreamReader:把一个以字节为导向的stream转换成一个以字符为导向的stream。
InputStreamReader 是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集 。
OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。
RandomAccessFile
RandomAccessFile:此类的实例支持对随机访问文件的读取和写入。随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过 getFilePointer 方法读取,并通过 seek 方法设置。
I/O应用的例子
- package JavaIO;
- import java.io.*;
- public class JavaIO{
- public static void main(String[] args) throws Exception {
- // 1b. 接收键盘的输入
- /*
- * 由于想以缓存字节读取方式从标准IO(键盘)中读取数据,所以要
- * 先把标准IO(System.in)转换成字符导向的stream,再进行BufferedReader封装。
- */
- BufferedReader stdin = new BufferedReader(new InputStreamReader(
- System.in));
- System.out.println("Enter a line:");
- System.out.println(stdin.readLine());
- // 2. 从一个String对象中读取数据
- /*
- * 要以字符的形式从一个String对象中读取数据,所以要产生一个StringReader类型的stream。
- */
- StringReader in2 = new StringReader("zhxing");
- int c;
- while ((c = in2.read()) != -1)
- System.out.println((char) c);
- in2.close();
- // 3. 从内存取出格式化输入
- // 把内存中的一个缓冲区作为DataInputStream使用
- try {
- DataInputStream in3 = new DataInputStream(new ByteArrayInputStream(
- "zhxing".getBytes()));
- while (true)
- System.out.println((char) in3.readByte());
- } catch (EOFException e) {
- System.out.println("End of stream");
- }
- // 4. 输出到文件
- /*
- * 对String对象s2读取数据时,先把对象中的数据存入缓存中,再从缓冲中进行读取;对TestIO.out文件进行操作时,
- * 先把格式化后的信息输出 到缓存中,再把缓存中的信息输出到文件中。
- */
- try {
- BufferedReader in4 = new BufferedReader(new StringReader("zhxing"));
- PrintWriter out1 = new PrintWriter(new BufferedWriter(
- new FileWriter("F:\\java\\ TestIO.out")));
- int lineCount = 1;
- String s = null;
- while ((s = in4.readLine()) != null)
- out1.println(lineCount++ + ":" + s);
- out1.close();
- in4.close();
- } catch (EOFException ex) {
- System.out.println("End of stream");
- }
- // 5. 数据的存储和恢复
- /*
- * 对Data.txt文件进行输出时,是先把基本类型的数据输出屋缓存中,再把缓存中的数据输出到文件中;对文件进行读取操作时,先把文件中的数据读取到缓存中,再从缓存中以基本类型的形式进行读取。注意in5.readDouble()这一行。因为写入第一个writeDouble(),所以为了正确显示。也要以基本类型的形式进行读取。
- */
- try {
- DataOutputStream out2 = new DataOutputStream(
- new BufferedOutputStream(new FileOutputStream(
- "F:\\java\\ Data.txt")));
- out2.writeDouble(3.1415926);
- out2.writeChars("\nThas was pi:writeChars\n");
- out2.writeBytes("Thas was pi:writeByte\n");
- out2.close();
- DataInputStream in5 = new DataInputStream(new BufferedInputStream(
- new FileInputStream("F:\\java\\ Data.txt")));
- BufferedReader in5br = new BufferedReader(
- new InputStreamReader(in5));
- System.out.println(in5.readDouble());
- System.out.println(in5br.readLine());
- System.out.println(in5br.readLine());
- } catch (EOFException e) {
- System.out.println("End of stream");
- }
- // 6. 通过RandomAccessFile操作文件
- // 通过RandomAccessFile类对文件进行操作。
- RandomAccessFile rf = new RandomAccessFile("F:\\java\\ rtest.dat", "rw");
- for (int i = 0; i < 10; i++)
- rf.writeDouble(i * 1.414);
- rf.close();
- rf = new RandomAccessFile("F:\\java\\ rtest.dat", "r");
- for (int i = 0; i < 10; i++)
- System.out.println("Value " + i + ":" + rf.readDouble());
- rf.close();
- rf = new RandomAccessFile("F:\\java\\ rtest.dat", "rw");
- rf.seek(5 * 8);
- rf.writeDouble(47.0001);
- rf.close();
- rf = new RandomAccessFile("F:\\java\\ rtest.dat", "r");
- for (int i = 0; i < 10; i++)
- System.out.println("Value " + i + ":" + rf.readDouble());
- rf.close();
- }
- }