从装饰设计模式角度学习JavaIO字节流
之前已经学习过装饰设计模式,下面从这个角度来学习一下Java I/O的字节流。
装饰设计模式有几个要点:
1、装饰基类Deractor应该与被装饰类实现同一接口,或者继承同一父类。
2、装饰基类通过构造函数接收被装饰类对象,注意构造函数的类型为:实现的同一接口或者继承的同一父类的类型。
目的是为了装饰类能够装饰其所有子类对象,并利用多态特性(父类引用指向子类实例对象,通过该引用调用方法将执行子类的覆盖方法)实现多重装饰的链式嵌套。
3、通过继承的方式扩展该装饰基类,可以自由定义多种装饰类型。
下面看一下Java I/O中主要的字节流类的规划:
InputStream、OutputStream位于类结构的最顶层,分别表示输入字节流和输出字节流。
首先,流是一个抽象的概念,用于屏蔽底层I/O设备对于数据处理的细节,输入流可以理解为有能力产出数据的数据源对象,输出流可以理解为有能力接收数据的接收端对象。
按照数据源的不同,InputStream通过继承的方式产生了如图所示的以下多种。按照数据去处的不同,OutputStream同样通过继承的方式产生了多种具体实现类。
通常,为了提升I/O操作的效率,我们需要采用缓冲技术。
形象的理解:一个人很渴要喝水,但是水源只能一滴一滴地出水,那么他是应该在那里一滴一滴地喝水呢,还是应该用一个杯子放在那里接水,然后取干一会其它事情之后再回来一口将水喝掉呢?
因此,我们就需要在原有输入输出流类中增加一个缓冲功能,怎么加?很容易想到,通过继承的方式增加功能。就输入流而言,根据数据源的不同有很多实现类,那么需要对每一个实现类进行继承来改写原有的功能达到增强的效果吗?这样显然不可取,这便体现了装饰设计模式的优点了。
上图中,继承自InputStream的FilterInputStream,继承自OutputStream的FilterOutputStream,便是装饰设计模式的基类。
看一下FilterOutputStream的源码:
public
class FilterOutputStream extends OutputStream {
protected OutputStream out;
public FilterOutputStream(OutputStream out) {
this.out = out;
}
public void write(int b) throws IOException {
out.write(b);
}
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
public void write(byte b[], int off, int len) throws IOException {
if ((off | len | (b.length - (len + off)) | (off + len)) < 0)
throw new IndexOutOfBoundsException();
for (int i = 0 ; i < len ; i++) {
write(b[off + i]);
}
}
public void flush() throws IOException {
out.flush();
}
public void close() throws IOException {
try {
flush();
} catch (IOException ignored) {
}
out.close();
}
}
标准的装饰设计模式,通过构造函数
public FilterOutputStream(OutputStream out) {
this.out = out;
}
传入OutputStream对象的实例,用于接收其任意子类对象,之后的方法只是通过OutputStream对象提供一个简单的实现,用于被具体的装饰覆盖。
BufferedOutputStream以及DataOutputStream等等则是继承FilterOutputStream的具体装饰类型,来看一下其源码:
public
class BufferedOutputStream extends FilterOutputStream {
/**
* The internal buffer where data is stored.
*/
protected byte buf[];
protected int count;
public BufferedOutputStream(OutputStream out) {
this(out, 8192);
}
public BufferedOutputStream(OutputStream out, int size) {
super(out);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
/** Flush the internal buffer */
private void flushBuffer() throws IOException {
if (count > 0) {
out.write(buf, 0, count);
count = 0;
}
}
public synchronized void write(int b) throws IOException {
if (count >= buf.length) {
flushBuffer();
}
buf[count++] = (byte)b;
}
public synchronized void write(byte b[], int off, int len) throws IOException {
if (len >= buf.length) {
/* If the request length exceeds the size of the output buffer,
flush the output buffer and then write the data directly.
In this way buffered streams will cascade harmlessly. */
flushBuffer();
out.write(b, off, len);
return;
}
if (len > buf.length - count) {
flushBuffer();
}
System.arraycopy(b, off, buf, count, len);
count += len;
}
public synchronized void flush() throws IOException {
flushBuffer();
out.flush();
}
}
其内部定义了一个缓冲区buf[ ],如被覆盖的write()方法并不直接向接收端写数据,而是写到缓冲区中,如果数据量超过缓冲区的大小就flush()掉。
这样,便可以对任意的OutputStream对象添加缓冲功能。
这样也就能够更加理解:
DataOutputStream bof = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("")));
为什么这样写了吧?需要什么功能采用什么装饰,需要什么顺序的装饰也完全由自己控制。输入流同样方式理解。