Java I/O库结合装饰器设计模式的一点理解

一、概述

1.1 关于I/O库使用的设计模式-装饰器模式

《Thinking in Java》: 装饰器模式使用分层对象来动态透明地向单个对象中添加责任。装饰器指定包装在最初地对象周围地所有对象都具有相同的基本接口。某些事物是可装饰的,可以通过将其它类包装在这个可装饰对象的四周,从而将功能分层,这使得对装饰器的使用是透明的——无论对象是否被装饰,你都拥有一个可以向对象发送的公共消息集。

Java I/O库实际是很多功能的组合,所以使用装饰器设计模式。
至于装饰器设计模式,在我的另外一篇笔记中也进行了学习,这里就不再赘述,传送门:
浅谈装饰器设计模式

1.2 装饰器模式在I/O库中的实际应用

就以字节输入流系列为例:
InputStream是一个抽象类,在装饰器设计模式当中,充当了抽象构件的角色,它为所有被修饰的对象提供通用接口,通过查看源码也能知道这点。

FilterInputStream继承自InputStream,充当装饰角色(Decorator),该类应当持有一个构件角色对象的实例,在FilterInputStream中含有一个输入流对象的保护成员:

protected volatile InputStream in;

通过给构造方法传递InputStream子类对象的引用就可以初始化该保护成员,该保护成员其实就是JDK文档中I/O部分提到的underlying stream,详细可自己查看JDK文档。

FilterInputStream只有一个子类BufferedInputStream,其充当了具体装饰角色(Concrete Decorator),给具体构件提供额外的功能(如我们所知,BufferedInputStream给输入流提供了缓冲功能,减少了I/O次数)。

FileInputStream等则充当的是具体构件角色,可以把具体构件角色的对象传递给具体装饰器角色,如之前所示,其内部的保护成员in会被初始化,指向该具体构件角色的对象。典型的:

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));

1.3 BufferedInputStream实现原理简述

通过上面的分析我们知道BufferedInputStream实际就是一个装饰器,它为输入流提供了缓冲功能,实际上就是内置了一个字节数组buf,默认大小是8192,大小也可以自己指定,除此之外,在BufferedInputStream中还提供了其它一些比较关键的保护成员(指的是对于缓冲功能实现相对比较重要):

protected volatile byte buf[];
 /**
  * The index one greater than the index of the last valid byte in
  * the buffer.This value is always in the range 0 through buf.length;
  * elements buf[0]  through buf[count-1] contain buffered input data obtained from the underlying  
  * input stream.
  */
protected int count;

/**
 * The current position in the buffer. This is the index of the next
 * character to be read from the buf array.
 * This value is always in the range 0 through count. If it is less
 * than count, then buf[pos] is the next byte to be supplied as input;
 * if it is equal to count, then the  next read or skip operation will require
 * more bytes to be read from the contained  input stream.
 */
protected int pos;
protected int markpos = -1;
protected int marklimit;

其中两个比较关键的字段count和pos已经给出了注释,注释来源于JDK源代码的注释,count和pos其实就是两个指针,count指向buf中最后一个有效字节之后的那个位置,而pos指向buf中下一个要读取的位置(即pos处还未被读取)。

buf缓冲数组中的数据则来源于underlying stream即之前被初始化的in,在BufferedInputStream中还增加了一个私有函数fill(),该函数实际上就是完成了从输入流到缓冲数组的数据的读取,其功能依赖于count,pos,mark等指针,每当pos>=count(这说明buf中的有效数据已经读取完毕)就会调用fill()方法,往buf数组中填充数据,若buf中除了有效数据还有位置,则会紧接着有效位置填充来自于in的数据。

那么为什么BufferedInputStream能够有效减少I/O次数,从而提高性能呢?
因为buf数组的存在,每一次其内部的输入流in调用read()方法,实际上都会读写很多个字节的数据,并且将其缓冲在buf数组当中,因此当调用BufferedInputStream的read方法时实际上就是从buf数组中读数据,但是如果我们直接使用输入流的read方法,那么每一次读取都要付出昂贵的磁盘IO的代价。因此,显然,从内存中读数据要比从磁盘上读数据快得多(通常每次读取可能会少上数十或者上百个时钟周期)。

1.4 I/O库流的分类

可以划分为字符流和字节流。字符流包括Reader,Writer及其子类,字节流包括InputStream,OutputStream及其子类,一般读取文本文件可以用字符流,图像等其它文件则可以使用字节流。

二、简单使用方式

2.1 字符流

2.1.1 控制台输入
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

因为System.in是InputStream类型,而BufferedReader需要接受Reader类型,因此需要InputStreamReader来进行字节流与字符流之间的转换,实际上InputStreamReader内置了一个解码器(Decoder),因此可以指定字符集来对字节流进行解码。

2.1.2 读取文本文件
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader("test.txt"));
        int c;
        while ((c = br.read()) != -1) {
            System.out.print((char) c);
        }
        br.close();
    }
}

因为close()方法声明抛出了IOException异常,必须对其处理,这里选择继续向上层调用栈抛出。

2.1.3 向文件写入文本
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedWriter bw = new BufferedWriter(new FileWriter("write_in.txt"));
        bw.write("感谢您看我的博客文章!");
        bw.close();
    }
}

2.2 字节流

2.2.1 复制文件
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.jpg"));
        BufferedOutputStream bos= new BufferedOutputStream(new FileOutputStream("test_copy4.jpg"));
        byte[] bytes=new byte[1024];
        int len; // 读取的有效字节数
        while((len=bis.read(bytes))!=-1){
            bos.write(bytes,0,len); // 将有效字节续写在文件后
        }
        bos.close();
        bis.close();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值