Java输入输出流体系

1.* 输入/输出流体系

在Java的字符流和字节流中,介绍了输入/输出流的4个抽象基类,并介绍了4个访问文件的节点流的用法。通过4个抽象基类的用法我们发现,4个基类使用起来有些烦琐,这就需要借助于处理流了。

1.*.& 处理流的用法

下图显示了处理流的功能:

image

它可以隐藏底层设备上节点流的差异,并对外提供更加方便的输入/输出方法,让程序员只需关心高级流的操作。

因此,我们使用处理流时的典型思路是,使用处理流来包装节点流,程序通过处理流来执行输入/输出功能,让节点流与底层的I/O设备、文件交互。

实际上,识别处理流非常简单,只要流的构造器参数不是一个物理节点,而是已经存在的流,那么这种流就一定是处理流;而所有节点流都是直接以物理IO节点作为构造器参数的。

关于使用处理流的优势,归纳起来就是2点:

  1. 对开发人员来说,使用处理流进行输入/输出操作更简单;
  2. 使用处理流的执行效率更高。

下面程序使用PrintStream处理流来包装OutputStream,使用处理流后的输出流在输出时将更加方便:

查看代码

上面程序中的代码先定义了一个节点输出流FileOutputStream,然后程序使用PrintStream包装了该节点输出流,最后使用PrintStream……PrintStream的输出功能非常强大,前面程序中一直使用的标准输出System.out的类型就是PrintStream。

提示:由于PrintStream类的输出功能非常强大,通常如果我们需要输出文本内容,都应该将输出流包装成PrintStream后进行输出。

从前面的代码可以看出,程序使用处理流非常简单,通常只需要在创建处理流时传入一个节点流作为构造器参数即可,这样创建的处理流就是包装了该节点流的处理流。

注意:在使用处理流包装了底层节点流之后,关闭输入/输出流资源时,只要关闭最上层的处理流即可。关闭最上层的处理流时,系统会自动关闭被该处理流包装的节点流。

1.*.& 输入/输出流体系

Java的输入/输出流体系提供了近40个类,这些类看上去杂乱而没有规律,但如果我们将其按功能进行分类,则不难发现其是非常规则的。下表显示了Java输入/输出流体系中常用的流分类:

image

从表中可以看出,Java的输入/输出流体系之所以如此复杂,主要是因为Java为了实现更好的设计,它把IO流按功能分成了许多类,而每类中又分别提供了字节流和字符流(当然有些流无法提供字节流,有些流无法提供字符流),字节流和字符流里又分别提供了输入流和输出流两大类,所以导致整个输入/输出流体系格外复杂。

通常来说,我们认为字节流的功能比字符流的功能强大,因为计算机里所有的数据都是二进制的,而字节流可以处理所有的二进制文件,但问题是,如果使用字节流来处理文本文件,则需要使用合适的方式把这些字节转换成字符,这就增加了编程的复杂度。所以通常有一个规则:如果进行输入/输出的内容是文本内容,则应该考虑使用字符流;如果进行输入/输出的内容是二进制内容,则应该考虑使用字节流。

提示:我们常常会把计算机的文件分为文本文件和二进制文件两大类,所有能用记事本打开并看到其中字符内容的文件称为文本文件,反之则称为二进制文件。但实质是,计算机里的所有文件都是二进制文件,文本文件只是二进制文件的一种特例,当二进制文件里的内容恰好能被正常解析成字符时,则该二进制文件就变成了文本文件。更甚至于,即使是正常的文本文件,如果打开该文件时强制使用了“错误”的字符集,例如使用EditPlus打开刚刚生成的poem.txt文件时指定使用UTF-8字符集,则将看到打开的poem.txt文件内容变成了乱码。因此,如果希望看到正常的文本文件内容,则必须在打开文件时与保存文件时使用相同的字符集(Windows下简体中文默认使用GBK字符集,而Linux下简体中文默认使用UTF-8字符集)。

上表仅仅总结了输入/输出流体系中位于java.io包下的流,还有一些诸如AudioInputStream、CipherInputStream、DeflaterInputStream、ZipInputStream等具有访问音频文件、加密/解密、压缩/解压等功能的字节流,它们具有特殊的功能,位于JDK的其他包下,本书不打算介绍这些特殊的IO流。

上表中还列出了一种以数组为物理节点的节点流,字节流以字节数组为节点,字符流以字符数组为节点;这种以数组为物理节点的节点流除了在创建节点流对象时需要传入一个字节数组或者字符数组之外,用法上与文件节点流完全相似。与此类似的是,字符流还可以使用字符串作为物理节点,用于实现从字符串读取内容,或将内容写入字符串(用StringBuffer充当字符串)的功能。

下面程序示范了使用字符串作为物理节点的字符输入/输出流的用法:

查看代码

上面程序与前面使用FileReader和FileWriter的程序基本相似,只是在创建StringReader和StringWriter对象时传入的是字符串节点,而不是文件节点。由于String是不可变的字符串对象,所以StringWriter使用StringBuffer作为输出节点。

表中列出了4个访问管道的流:PipedInputStream、PipedOutputStream、PipedReader、PipedWriter,它们都是用于实现进程之间通信功能的,分别是字节输入流、字节输出流、字符输入流和字符输出流。

表中的4个缓冲流则增加了缓冲功能,增加缓冲功能可以提高输入、输出的效率,增加缓冲功能后需要使用flush()才可以将缓冲区的内容写入实际的物理节点。表中的对象流主要用于实现对象的序列化。

1.*.& 转换流

输入/输出流体系中还提供了两个转换流,这两个转换流用于实现将字节流转换成字符流,其中InputStreamReader将字节输入流转换成字符输入流,OutputStreamWriter将字节输出流转换成字符输出流。

问:怎么没有把字符流转换成字节流的转换流呢?

答:你这个问题很“聪明”,似乎一语指出了Java设计的遗漏之处。想一想字符流和字节流的差别:字节流比字符流的使用范围更广,但字符流比字节流操作方便。如果有一个流已经是字符流了,也就是说,是一个用起来更方便的流,为什么要转换成字节流呢?反之,如果现在有一个字节流,但我们知道这个字节流的内容都是文本内容,那么把它转换成字符流来处理就会更方便一些,所以Java只提供了将字节流转换成字符流的转换流,没有提供将字符流转换成字节流的转换流。

下面以获取键盘输入为例来介绍转换流的用法。Java使用System.in代表标准输入,即键盘输入,但这个标准输入流是InputStream类的实例,使用不太方便,而且键盘输入内容都是文本内容,所以可以使用InputStreamReader将其转换成字符输入流,普通的Reader读取输入内容时依然不太方便,我们可以将普通的Reader再次包装成BufferedReader,利用BufferedReader的readLine()方法可以一次读取一行内容。如下程序所示:

查看代码

上面程序中的“BufferedReader br = new BufferedReader(reader)”负责将System.in包装成BufferedReader,BufferedReader流具有缓冲功能,它可以一次读取一行文本,以换行符为标志,如果它没有读到换行符,则程序阻塞,等到读到换行符为止。运行上面程序可以发现这个特征,当我们在控制台执行输入时,只有按下回车键,程序才会打印出刚刚输入的内容。

提示:由于BufferedReader具有一个readLine()方法,可以非常方便地一次读入一行内容,所以经常把读取文本内容的输入流包装成BufferedReader,用来方便地读取输入流的文本内容。

1.*.& 推回输入流

在输入/输出流体系中,有两个特殊的流与众不同,就是PushbackInputStream和PushbackReader,它们都提供了如下3个方法:

  • void unread(byte[]/char[] buf):将一个字节/字符数组内容推回到推回缓冲区里,从而允许重复读取刚刚读取的内容。
  • void unread(byte[]/char[] b, int off, int len):将一个字节/字符数组里从off开始,长度为len字节/字符的内容推回到推回缓冲区里,从而允许重复读取刚刚读取的内容。
  • void unread(int b):将一个字节/字符推回到推回缓冲区里,从而允许重复读取刚刚读取的内容。

细心的读者可能已经发现了这3个方法与InputStream和Reader中的3个read()方法一一对应,没错,这3个方法就是PushbackInputStream和PushbackReader的奥秘所在。

这两个推回输入流都带有一个推回缓冲区,当程序调用这两个推回输入流的unread()方法时,系统将会把指定数组的内容推回到该缓冲区里,而推回输入流每次调用read()方法时总是先从推回缓冲区读取,只有完全读取了推回缓冲区的内容后,但还没有装满read()所需的数组时才会从原输入流中读取。图中显示了这种推回输入流的处理示意图:

image

注意:虽然图中的推回缓冲区的长度看似比read()方法的数组参数的长度小,但实际上,推回缓冲区的长度与read()方法的数组参数的长度没有任何关系,完全可以更大。

根据上面的介绍可以知道,当我们创建一个PushbackInputStream和PushbackReader时需要指定推回缓冲区的大小,默认的推回缓冲区的长度为1。如果程序中推回到推回缓冲区的内容超出了推回缓冲区的大小,将会引发Pushback buffer overflow的IOException异常。

下面程序试图找出程序中的“new PushbackReader”字符串,当找到该字符串后,程序只是打印出目标字符串之前的内容:

查看代码

上面程序中的代码实现了将指定内容推回到推回缓冲区,于是当程序再次调用read()方法时,实际上只是读取了推回缓冲区的部分内容,从而实现了只打印目标字符串前面内容的功能。

原文地址:http://www.cnblogs.com/hzhiping/p/16904776.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性