对程序语言设计者来说,设计一个令人满意的I/O(输入/输出)系统,是件极艰巨的任务——《Thinking in Java》
流的概念
Java程序通过流来完成输入/输出,流是生产或消费信息的抽象。流通过Java的输入/输出系统与物理设备连接,尽管与他们连接的物理设备不尽相同,所有流的行为具有同样的方式,这样,相同的输入/输出类和方法适用于所有类型的外部设备,这意味着一个输入流能够抽象成多种不同类型的输入:从磁盘文件,从键盘或从网络套接字。同样,一个输入流可以输出到控制台、磁盘文件或相连的网络。流是处理输入/输出的一个洁净的方法,例如它不需要代码理解键盘和网络的不同,Java中流的实现是在java.io包定义的类层次结构内部的。
输入/输出流概念
输入/输出时,数据在通信通道中流动。所谓“数据流(stream)”指的是所有数据通信通道之中数据的起点和终点,信息的通道就是一个数据流。只要是数据从一个地方“流”到另一个地方,这种数据流动的通道都可以成为数据流。
输入/输出是相对于程序来说的,程序在使用数据时所扮演的角色有两个:一个是源,一个是目的。若程序是数据流的源,即数据的提供者,那么这个数据流对程序来说就是一个“输出数据流”(数据从程序流出);若程序是数据的终点,那么这个数据流对程序而言就是一个“输入数据流”(数据从程序外流向程序)。
输入/输出类
在功能上分为两大类:输入流和输出流。
从结构上可分为字节流(以字节为处理单位或面向字节)和字符流(以字符为处理单位或面向字符)
字节流的输入流和输出流基础是InputStream和OutputStream这两个抽象类,字节流的输入输出操作由这两个类的子类实现,字符流是Java1.1版之后新增加的以字符为单位进行输入输出处理的流,字符流输入输出的基础的抽象类是Reader和Writer。
字节流和字符流
Java2定义了两种类型的流:字节流和字符流。字节流(byte stream)为处理字节的输入和输出提供了方便的方法。例如使用字节流读取或写入二进制数据。字符流(character stream)为字符的输入输出处理提供了方便,他们采用了统一的编码标准,因而可以国际化,当然在某些场合,字符流比字节流更有效。
Java的原始版本(Java1.0)不包括字符流,因此在这个版本下所有的输入输出都是以字节为单位的,Java1.1中加入了字符流。
需要声明:在最底层,所有的输入/输出都是字节形式的,基于字符的流只是为了处理字符提供方便有效的方法。
流的分类
两种基本流是:出入流(InputStream)和输出流(OutputStream)。可从中读出一一系列字节的对象称为输入流,而能向其中写入一系列字节的对象称为输出流。
节点流:从特定的地方读写的流类,例如:磁盘或一块内存区域。
-
过滤流:使用节点流作为输入或输出。过滤流是使用一个已经存在的输入流或输出流连接创建的。
InputStream
InputStream中包含一整套字节输入流需要的方法。可以完成最基本的从输入流读入数据的功能,当Java程序需要外设的数据时,可根据数据的不同形式,创建一个适当的InputStream子类类型的对象来完成与该外设的连接,然后在调用执行这个流类对象的特定输入方法来实现对相应外设的输入操作。
InputStream类子类对象自然也继承了InputStream类的方法,常用的方法有:读数据的方法read(),获取输入流字节数的方法available(),定位输入位置指针的方法skip()、reset()、mark()等。
OutputStream
三个基本的写方法:
- abstract void write(int b):往输出流中写入一个字节。
- void write(byte[] b):往输出流中写入数组b中的所有字节。
- void write(byte[] b,int off,int len):往输出流中写入数组b中从偏移量off开始的len个字节的数据。
其他方法: - void flush():刷新输出流,强制缓冲区中的输出字节被写出。
- void close():关闭输出流,释放和这个流相关的系统资源。
OutputStream是定义了流式字节输出模式的抽象类,该类的所有方法返回一个void值且出错情况下引发一个IOException异常。
过滤流
在InputStream类和OutputStream类子类中,FilterInputStream和FilterOutputStream过滤流抽象类又派生出DataInputStream和DataOutputStream数据输入输出流类等子类。
Java I/O库的设计原则
Java的I/O库提供一个称做链接的机制,可以将一个流与另一个流首尾相接,形成一个流管道的链接。这种机制实际上是一种被称为Decorator(装饰)设计模式的应用。
通过流的链接,可以动态的增加流的功能,而这种功能的增加是通过组合一些流的基本功能而动态获取的。
我们要获取一个I/O对象,往往需要产生多个I/O对象,这也是JavaI/O库不太容易掌握的原因,但在I/O库中Decorator设计模式的运用,给我们提供了实现上的灵活性
装饰设计模式(Decorator)
- 装饰设计模式又名包装(Wrapper)模式
- 装饰设计模式以对客户端透明的方式扩展对象的功能,是继承关系的一种替代方案。
- 装饰设计模式已对客户端透明的方式动态的给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。
- 装饰设计模式可以在不创造更多子类的情况下,将对象的功能加以扩展。
- 装饰设计模式把客户端的调用委派到被装饰类,装饰设计模式的关键在于这种扩展完全是透明的。
- 装饰设计模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能,它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
装饰模式的角色:
- 抽象构件角色(Component):给出一个抽象接口,以规范准备接收附加责任的对象。
- 具体构件角色(Concrete Component):定义一个将要接收附加责任的类。
- 装饰角色(Decorator):持有一个构件(Component)对象的引用,并定义一个与抽象构件接口一致的接口
- 具体装饰角色(Concrete Decorator):负责给构件对象“贴上”附加的责任。
装饰模式的特点:
- 装饰对象和真实对象有相同的接口,这样客户端对象就可以以和真实对象相同的方式和装饰对象交互。
- 装饰对象包含一个真实对象的引用(reference)
- 装饰对象接收所有来自客户端的请求:它把这些请求转发给真实的对象。
- 装饰对象可以在转发这些请求以前或以后增加一些附加功能,这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。
编码实现装饰设计模式:
1、首先创建一个接口Component,相当于抽象构件角色
public interface Component {
void doSomething();
}
2、创建一个类ConcreteComponent实现Component 接口,相当于具体构件角色
public class ConcreteComponent implements Component {
@Override
public void doSomething() {
System.out.println("功能A");
}
}
3、创建Decorator类,同样实现Component接口,相当于装饰角色,此类为装饰设计模式的核心。
装饰角色必须满足两点要求:
- A、装饰角色必须实现抽象构件角色
- B、装饰角色要持有一个抽象构件角色的引用
public class Decorator implements Component {
private Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void doSomething() {
component.doSomething();
}
}
4、在创建两个具体的装饰角色ConcreteDecorator1和ConcreteDecorator2,两个角色都继承Decorator
public class ConcreteDecorator1 extends Decorator {
public ConcreteDecorator1(Component component) {
super(component);
}
@Override
public void doSomething() {
super.doSomething();
doAnotherThing();
}
private void doAnotherThing(){
System.out.println("功能B");
}
}
public class ConcreteDecorator2 extends Decorator {
public ConcreteDecorator2(Component component) {
super(component);
}
@Override
public void doSomething() {
super.doSomething();
doAnotherThing();
}
private void doAnotherThing(){
System.out.println("功能C");
}
}
5、创建一个Client客户端,用于装饰设计模式的使用
public class Client {
public static void main(String[] args) {
Component component = new ConcreteDecorator2(new ConcreteDecorator1(new ConcreteComponent()));
component.doSomething();
}
}
装饰模式VS继承
装饰模式:
- 用来扩展特定对象的功能
- 不需要子类
- 动态
- 运行时分配职责
- 防止由于子类而导致的复杂和混乱
- 更多的灵活性
- 对于一个给定的对象,同时可能有不同的装饰对象,客户端可以通过它的需要,选择合适的装饰对象进行操作。
继承:
- 用来扩展一类对象的功能
- 需要子类
- 静态
- 编译时分派职责
- 导致子类体系过于臃肿
- 缺乏灵活性
装饰设计模式(Decorator)的适用性
想要透明并且动态的给对象增加新的职责(方法),而又不会影响其他对象
给对象增加的职责在未来可能会发生变化
用子类扩展功能不切实际的情况下