对IO包类层次的理解

这两天一直在学习java的io包,一直觉得类好多,继承又复杂,今天学习了适配器模式和装饰器模式之后,突然对整个io包的设计思路,有了豁然开朗的感觉。

简单总结一下内心的想法吧:
首先,肯定是从最根本的说起,IO包中一个重要的概念是流:"Stream代表的是任何有能力产出数据的数据源,或是任何有能力接收数据的接收源。",一般来说包括:字节数组,String对象,文件,管道,Internet连接等等。

不管是输入流还是输出流,都包含了两种类型:字节流(?Stream)和字符流(?Reader)。至于说为什么有两种流,这个可能要追溯到Java设计的初期,反正有一条事实上,字符流对象Reader和Writer是Java1.1才加进来的,为的是支持兼容Unicode和面向字符的I/O功能。另外还有的就是新的设计比旧设计速度要快。

(这里必须插一句,Java内部的字符都是Unicode编码的,因此每个字符占两个字节,16位。字符流,也就是双字节流,个人理解,仅供参考)

下面来看看InputStream的继承结构吧:
InputStream:
  • ByteArrayInputStream:把内存中的一个缓冲区作为InputStream使用
  • StringBufferInputStream:把一个String对象作为InputStream
  • FileInputStream:把一个文件作为InputStream,实现对文件的读取操作
  • PipedInputStream:实现了pipe的概念,主要在线程中使用
  • SequenceInputStream:把多个InputStream合并为一个InputStream
  • FilterInputStream:抽象类,作为“装饰器”的接口。
OutputStream就不说了,道理是一样的,InputStream是所有流的基类,继承它的子类分别是相应不同的数据源的实现。这里我想强调的只有两个类:

第一,StringBufferInputStream,这个类是Deprecated了,在Java 1.1中,为什么,很简单,因为String是16位的了,这个根本不适用了,取代它的是StringReader类。在Java Doc中,还有这样一段话:

“此类允许应用程序创建输入流,在该流中读取的字节由字符串内容提供。应用程序还可以使用 ByteArrayInputStream 从 byte 数组中读取字节。只有字符串中每个字符的低八位可以由此类使用。”

再明显不过了。

第二,FilterInputStream,这个类是装饰器模式在Java的io包中的一个应用。它为所有“装饰器”类提供了一个基类。我感觉它的作用就仅限于这里了。看看他的子类:

  • DataInputStream:从stream中读取基本类型(int、char等)数据。
  • BufferedInputStream:使用缓冲区
  • LineNumberInputStream:会记录input stream内的行数,然后可以调用getLineNumber()和setLineNumber(int)
  • PushbackInputStream:很少用到,一般用于编译器开发

其实我在想为什么一定要加一个FilterInputStream类的,其实是不是让FilterInputStream的子类直接继承自InputStream也可以实现装饰器模式呢?欢迎大家讨论啊。

我在写下这段话的一分钟后查看了字符流类Reader的装饰器模式实现情况,发现:在Reader那边的大家族中,装饰器类FilterReader是没有任何子类的,而在Stream家族中的装饰器类,如BufferedInputStream对应的BufferedReader是直接继承与Reader的,同样的他是实现了装饰器模式:BufferedReader(Reader in)

看到这种情况,我只是有种感觉,就是Java设计者也会出现一丝的混乱啊。不过到底是设计者别有心意,还是就是画蛇添足(多了个FilterInputStream)呢?希望大家讨论啊。

可能有人会问:其实为什么要用装饰器模式呢?用它来做什么?
这就需要了解一下装饰器模式了。
装饰器模式是为了动态地,透明地为对象添加职责。
其产生的原因是直接使用扩展子类的方法,可能会因为满足各种需求的组合太多,而导致子类的爆炸,使用装饰器模式可以解决这个问题。
根据我的理解,装饰器模式就是像在 被装饰类 之外 有包了一层,使得被装饰类的功能得到扩展,请参见:http://student.csdn.net/space.php?uid=37718&do=blog&id=1807

好了,至此,关于字节流类,我们已经基本上有一个框架了。基类是InputStream,由它派生了基于不同数据源的子类(看回上面)和一个特殊的装饰器基类FilterInputStream。然后从FilterInputStream派生出去的装饰器类去实现一些特殊的功能,如DataInputStream,读取基本类型数据,看了JavaDoc就知道,这个类多了诸如readFloat,readInt这样的方法。最后看看这个图吧。


-----------------------------我是华丽丽的分割线---------------------------------------------------

好了,现在到了字符流家族了,基类是Reader,看看他的定义:

“用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。但是,多数子类将重写此处定义的一些方法,以提供更高的效率和/或其他功能。”

然后是它的子类们:
  • CharArrayReader:与ByteArrayInputStream对应
  • StringReader:与StringBufferInputStream对应
  • FileReader:与FileInputStream对应
  • PipedReader:与PipedInputStream对应
呵呵。要注意的是每一个Reader子类都与一个InputStream子类相对应的。这就是对应不同数据源的字符流类了。
当然,我们不能忽略了FilterReader,当然他也是一个子类,与FilterInputStream所对应,但是我们可以发现,作为装饰器类的基类,他派生的子类就只有PushBackReader一个,其他的装饰类,如BufferdReader,是直接继承于Reader的。

在我写完这段话之后,查看了一下JavaDoc,发现事实还没有这么简单,还是看看继承树吧:
java.io. Reader (implements java.io. Closeable, java.lang. Readable) 我个人感觉这样的设计有几点值得注意和深入思考,为什么java设计者要这样来设计类层次?
  1. 注意到InputStreamReader,这是一个适配器类,关于适配器的讨论可见:
    http://student.csdn.net/space.php?uid=37718&do=blog&id=1807   使用适配器是为了在字节流和字符流类,这两种不兼容的类之间建立兼容。事实上,我个人感觉适配器也是在被适配类之外包了一层,使得适配器类能够提供兼容的接口。看一个实际例子就明白了: //1b. 接收键盘的输入
            BufferedReader stdin =
                new BufferedReader(
                    new InputStreamReader(System.in));
            System.out.println("Enter a line:");
            System.out.println(stdin.readLine());   注意到这里其实同时用了适配器模式和装饰器模式,我就不多说了。
  2. 注意其他的子类,可以分为两类,一类是非装饰器类 { java.io.CharArrayReader  , java.io.StringReader , java.io.PipedReader java.io.FileReader },另外的就是装饰器类。 怎么区分呢,看构造器就知道了。装饰器类的构造器都会以一个Reader作为传入参数。当然了,实际应用的时候一般都是传入Reader的子类InputStreamReader这个适配器。 看上面的例子。                     不过我想说的是,注意装饰器类的继承结构,实在是非常玄妙。理论上,装饰器类都应该继承于FilterReader,但是实际上,他们呈现出无规则的层次结构,除了PushBackReader是继承于FilterReader外,BufferdReader直接继承于Reader,而LineNumberReader继承于BufferedReader。我在这里给出我的一点猜想,估计是LineNumberReader重用了BufferedReader的close, markSupported, ready三个方法。 如果有同学知道这里面的奥妙,请指教一二。                                                      至于FileReader,他为什么要继承与InputStreamReader,估计就是为了方便,因为想这样写,也是可以的                                            BufferedReader file=
                new BufferedReader(
                    new InputStreamReader(new FileInputStream(filename))); 
    事实上,在javadoc中还有这样一段话:“用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。要自己指定这些值,可以先在 FileInputStream 上构造一个 InputStreamReader。” 要知道InputStreamReader的构造函数是可以指定字符集的,so~
花了两天时间,理了一下IO包的结构,发现真的还是挺有意思的,希望大家可以从中分享到知识,也欢迎拍砖。

(完)








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值