Java IO 完全总结


JavaJavaJavaJava IO IO IO IO 完全总结 本篇主要讲述 IO 相关的内容,主要包括:与IO 相关的简单的历史背景知识;Java IO 的简单分类;与IO 设计相关的两个模式;同时列举几个简单的例子;分析 其中几个实现的源代码;最后给大家一些简单扩展的例子。治学先治史,下面我们先从简单的历史开始吧! 一、 历史背景 “对语言设计人员来说,创建好的输入/输出系统是一项特别困难的任务。” ――《Think in Java》 无论是系统、还是语言的设计中 IO 的设计都是异常复杂的。面临的最大的挑战一般是如何覆盖所有可能的因素,我们不仅仅要考虑文件、控制台、网络、内存等 不同的种类,而且要处理大量的不同的读取方式,如:顺序读取、随机读取,二进制读取、字符读取,按行读取、按字符读取…… Linux 是第一个将设备抽象为文件的操作系统,在 Linux 中所有的外部设备都可以用读取文件的方法读取,这样编程人员就可以以操作文件的方法操作任何设备。 C++在IO 方面也做了一些改进――引进了流的概念,我们可以通过 cin、cout 读写一些对象。Java 语言在 IO 设计方面取得较大的成功,它是完全面向对象的,主要 采用装饰器模式避免大量的类,包括了最大的可能性,提供了较好的扩展机制…… “Java 库的设计者通过创建大量类来攻克这个难题。事实上,Java 的IO 系统采用了如此多的类,以致刚开始会产生不知从何处入手的感觉(具有讽刺意味的是, Java 的IO 设计初衷实际要求避免过多的类)。” 上面一段来自《Think in Java》,确实很多初学者刚刚学习 java 的IO 时会比较茫然,不过等我们知道装饰器模式 (Decorator)的用意、场景及其在 Java 的IO 包中的使用,你可能会真正领会整个 IO 的FrameWork。 二、IOIOIOIO的分类 Java IO 一般包含两个部分:1.java.io 包中堵塞型 IO;2.java.nio 包中的非堵塞型 IO,通常称为 New IO。学过操作系统的朋友都知道系统运行的瓶颈一般在于 IO 操作,一般打开某个 IO 通道需要大量的时间,同时端口中不一定就有足够的数据,这样 read 方法就一直等待读取此端口的内容,从而浪费大量的系统资源。有人 也许会提出使用 java 的多线程技术啊!但是在当前进程中创建线程也是要花费一定的时间和系统资源的,因此不一定可取。Java New IO 的非堵塞技术主要采用了 Observer 模式,就是有一个具体的观察者和=监测 IO 端口,如果有数据进入就会立即通知相应的应用程序。这样我们就避免建立多个线程,同时也避免了 read 等待 的时间。不过本篇主要讲述 java 的堵塞型 IO,就是我们通常应用的那个包。 打开你的 java.io 包你可以看到 Java 的IO 包含大量的类和接口(JDK1.6 中包含 83 个类或者接口),如此众多的类和接口似乎无从下手。下面就将 IO 简单地分 类。Java 的IO 主要包含三个部分:1.流式部分――IO 的主体部分;2.非流式部分――主要包含一些辅助流式部分的类,如:File 类、RandomAccessFile 类和 FileDescriptor 等类;3.文件读取部分的与安全相关的类,如:SerializablePermission 类。以及与本地操作系统相关的文件系统的类,如:FileSystem 类和 Win32FileSystem 类和WinNTFileSystem 类。 流式部分可以概括为:两个对应一个桥梁。两个对应指:1.字节流(Byte Stream)和字符流(Char Stream)的对应;2.输入和输出的对应。一个桥梁指:从 字节流到字符流的桥梁。对应于输入和输出为 InputStreamReader 和OutputStreamWriter。 在流的具体类中又可以具体分为:1.介质流(Media Stream 或者称为原始流 Raw Stream)――主要指一些基本的流,他们主要是从具体的介质上,如:文件、 内存缓冲区(Byte 数组、Char 数组、StringBuffer 对象)等,读取数据;2.过滤流(Filter Stream)――主要指所有 FilterInputStream/FilterOutputStream 和 FilterReader/FilterWriter 的子类,主要是对其包装的类进行某些特定的处理,如:缓存等。 三、IOIOIOIO中的流 流具有最基本的特点:“One dimension , one direction .” 即流是一维的,同时流是单向的。关于维和我们通常说的一维长度,二维平面,三维空间,四维时空…… 是同一个概念,流就是一维的。单向就是只可以一个方向(按顺序从头至尾依次)读取,不可以读到某个位置,再返回前面某个位置。流的概念和实际水流的概念基本 一致,水只可以从高向低一个方向流动。我们某时在目地喝了一口水,下次在同一个地点喝水已经不是当时的那片水了。 流的这种特性在 JMS(Java Message Service)的API 设计中得到了体现。JMS 是J2EE 平台下面向消息中间件的一个标准。(关于中间件技术有机会和大家 探讨)JMS 中有五种具体类型的消息,这些消息一般分为两类:1.流式的消息――包含ByteMessage 和StreamMessage;2.非流式的消息――包含TextMessage、 ObjectMessage 和MapMessage。我们在明白 IO 中流的特点后,基本可以明白 JMS API 设计者的意图。 可能有些场合我们需要在文件中随机插入数据、在流中来来回回地执行某些操作,这时候我们绝对不可以使用流相关的对象。很幸运 JDK 的设计者为我们设计了 一个单独的类 RandomAccessFile,它可以完成打开、关闭文件、以基本数据类型的方式读取数据、读取下一个行、以 UTF 等格式读取数据、写入各种类型的数据、 比较特殊的是他可以通过文件指针的 seek 方法让文件指针移到某个位置,可以通过 getFilePointer 方法得到当前指针的位置、可以通过 length()方法得到当前文件 的容量、通过 getFD 得到FileDescriptor 对象,通过 getChannel 方法得到 FileChannel 对象,从而和 New IO 整合。()()() 下面比较简单的分析 IO 中的各个对象吧! 3.13.13.13.1 IOIOIOIO中的输入字节流 下面是 IO 中输入字节流的继承图。 o InputStreamInputStreamInputStreamInputStream o ByteArrayInputStreamByteArrayInputStreamByteArrayInputStreamByteArrayInputStream o FileInputStreamFileInputStreamFileInputStreamFileInputStream o FilterInputStreamFilterInputStreamFilterInputStreamFilterInputStream o BufferedInputStreamBufferedInputStreamBufferedInputStreamBufferedInputStream o DataInputStreamDataInputStreamDataInputStreamDataInputStream o LineNumberInputStreamLineNumberInputStreamLineNumberInputStreamLineNumberInputStream o PushbackInputStreamPushbackInputStreamPushbackInputStreamPushbackInputStream o ObjectInputStreamObjectInputStreamObjectInputStreamObjectInputStream o PipedInputStreamPipedInputStreamPipedInputStreamPipedInputStream o SequenceInputStreamSequenceInputStreamSequenceInputStreamSequenceInputStream o StringBufferInputStreamStringBufferInputStreamStringBufferInputStreamStringBufferInputStream 在上面的关系图中可以看出: 1.InputStream 是所有的输入字节流的父类,它是一个抽象类。 2. ByteArrayInputStream、StringBufferInputStream、FileInputStream 是三种基本的介质流,它们分别将 Byte 数组、StringBuffer、和本地文件中读取数据。 PipedInputStream 是从与其它线程共用的管道中读取数据,与 Piped 相关的知识会用专门的一小节讲解。 3. ObjectInputStream 和所有 FilterInputStream 的子类都是装饰流(装饰器模式的主角)。下表列出了这些流的功能及如何使用它们(具体使用在讲解完装饰器模 式后会举几个例子)。 基本输入字节流: 类 功能 如何构造 怎样使用 ByteArrayInputStream 将内存中的 Byte 数组适配 为一个 InputStream。 从内存中的 Byte 数组创建该 对象(2种方法) 一般作为数据源,会使用其 它装饰流提供额外的功能, 一般都建议加个缓冲功能。 StringBufferInputStream 将内存中的字符串适配为 一个 InputStream。 从一个 String 对象创建该对 象。底层的实现使用 StringBuffer。该类被 Deprecated。主要原因是 StringBuffer 不应该属于字 节流,所以推荐使用 StringReader。 一般作为数据源,同样会使 用其它装饰器提供额外的功 能。 FileInputStream 最基本的文件输入流。主要 用于从文件中读取信息。 通过一个代表文件路径的 String、File 对象或者 FileDescriptor 对象创建。 一般作为数据源,同样会使 用其它装饰器提供额外的功 能。 PipedInputStream 读取从对应 PipedOutputStream 写入的 数据。在流中实现了管道的 概念。 利用对应的 PipedOutputStream 创建。 在多线程程序中作为数据 源,同样会使用其它装饰器 提供额外的功能。 SequenceInputStream 将2个或者多个 InputStream 对象转变为 一个 InputStream. 使用两个 InputStream 或者 内部对象为 InputStream 的 Enumeration 对象创建该对 象。 一般作为数据源,同样会使 用其它装饰器提供额外的功 能。 FilterInputStream 给其它被装饰对象提供额 外功能的抽象类 主要子类见下表 装饰、输入字节流: 类 功能 如何构造 怎样使用 DataInputStream 一般和 DataOutputStream 配对 使用,完成基本数据类型 的读写。 利用一个 InputStream 构 造。 提供了大量的读取基本数据 类新的读取方法。 BufferedInputStream 使用该对象阻止每次读 取一个字节都会频繁操 作IO。将字节读取一个缓 存区,从缓存区读取。 利用一个 InputStream、或 者带上一个自定义的缓存区 的大小构造。 使用 InputStream 的方法读 取,只是背后多一个缓存的 功能。设计模式中透明装饰 器的应用。 LineNumberInputStream 跟踪输入流中的行号。可 利用一个 InputStream 构 以调用 getLineNumber( )和 setLineNumber(int)方 法得到和设置行号。 造。 紧紧增加一个行号。可以象 使用其它 InputStream 一样 使用。 PushbackInputStream 可以在读取最后一个 byte 后将其放回到缓存 中。 利用一个 InputStream 构 造。 一般仅仅会在设计 compiler 的scanner 时会 用到这个类。在我们的 java 语言的编译器中使用它。很 多程序员可能一辈子都不需 要。 3.23.23.23.2 IOIOIOIO中的输出字节流 下面是 IO 中输出字节流的继承图。 • OutputStream • ByteArrayOutputStream FileOutputStream FilterOutputStream • BufferedOutputStream DataOutputStream PrintStream ObjectOutputStream PipedOutputStream 在上面的关系图中可以看出:1.OutputStream 是所有的输出字节流的父类,它是一个抽象类。2. ByteArrayOutputStream、FileOutputStream 是两种基本 的介质流,它们分别向 Byte 数组、和本地文件中写入数据。PipedOutputStream 是向与其它线程共用的管道中写入数据, 3. ObjectOutputStream 和所有 FilterOutputStream 的子类都是装饰流。下表列出了输出字节流的功能及如何使用它们。 类 功能 如何构造 怎样使用 ByteArrayOutputStream 在内存中创建一个 buffer。所有写入此流中的 数据都被放入到此 buffer 中。 无参或者使用一个可选的初始 化buffer 的大小的参数构造。 一般将其和 FilterOutputStream 套接得 到额外的功能。建议首先和 BufferedOutputStream 套接 实现缓冲功能。通过 toByteArray 方法可以得到流 中的数据。(不通明装饰器的 用法) FileOutputStream 将信息写入文件中。 使用代表文件路径的 String、 File 对象或者 FileDescriptor 对象创建。还 可以加一个代表写入的方式是 否为 append 的标记。 一般将其和 FilterOutputStream 套接得 到额外的功能。 PipedOutputStream 任何写入此对象的信息都 被放入对应 PipedInputStream 对象的 缓存中,从而完成线程的通 信,实现了“管道”的概 念。具体在后面详细讲解。 利用 PipedInputStream 构造 在多线程程序中数据的目的地 的。一般将其和 FilterOutputStream 套接得 到额外的功能。 FilterOutputStream 实现装饰器功能的抽象类。 见下表 为其它 OutputStream 对象 增加额外的功能。 见下表 装饰输出字节流: 类 功能 如何构造 怎样使用 DataOutputStream 通常和 DataInputStream 配合使用,使用它可以写入 基本数据类新。 使用 OutputStream 构造 包含大量的写入基本数据类型 的方法。 PrintStream 产生具有格式的输出信息。 (一般地在 java 程序中 DataOutputStream 用于数 据的存储,即 J2EE 中持久 层完成的功能, PrintStream完成显示的功 能,类似于 J2EE 中表现层 的功能) 使用 OutputStream和一个可选 的表示缓存是否在每次换行时 是否 flush 的标记构造。还提 供很多和文件相关的构造方 法。 一般是一个终极(“final”) 的包装器,很多时候我们都使 用它! BufferedOutputStream 使用它可以避免频繁地向 IO写入数据,数据一般都 写入一个缓存区,在调用 flush 方法后会清空缓存、 一次完成数据的写入。 从一个 OutputStream 或者和一 个代表缓存区大小的可选参数 构造。 提供和其它 OutputStream 一致 的接口,只是内部提供一个缓 存的功能。 3.3 3.3 3.3 3.3 字节流的输入与输出的对应 在3.1 节讲过输入与输出的对应,下图表示字节流部分的输入与输出的对应关系。 上图中蓝色的为主要的对应部分,红色的部分就是不对应部分。我习惯上称之为“不入流”部分。紫色的虚线部分代表这些流一般要搭配使用。从上面的图中可以看 出Java IO 中的字节流是极其对称的。 “存在及合理”我们看看这些字节流中不太对称的几个类吧! 1. LineNumberInputStream 主要完成从流中读取数据时,会得到相应的行号,至于什么时候分行、在哪里分行是由改类主动确定的,并不是在原始中有这样一个行号。 在输出部分没有对应的部分,我们完全可以自己建立一个 LineNumberOutputStream,在最初写入时会有一个基准的行号,以后每次遇到换行时会在下一行添加一个 行号,看起来也是可以的。好像更不入流了。 2. PushbackInputStream 的功能是查看最后一个字节,不满意就放入缓冲区。主要用在编译器的语法、词法分析部分。输出部分的 BufferedOutputStream 几乎实现 相近的功能。 3. StringBufferInputStream 已经被 Deprecated,本身就不应该出现在 InputStream 部分,主要因为 String 应该属于字符流的范围。已经被废弃了,当然输出部分 也没有必要需要它了!还允许它存在只是为了保持版本的向下兼容而已。 4. SequenceInputStream 可以认为是一个工具类,将两个或者多个输入流当成一个输入流依次读取。完全可以从 IO 包中去除,还完全不影响 IO 包的结构,却让其更 “纯洁”――纯洁的 Decorator 模式。 5. PrintStream 也可以认为是一个辅助工具。主要可以向其他输出流,或者FileInputStream 写入数据,本身内部实现还是带缓冲的。本质上是对其它流的综合运用的 一个工具而已。一样可以踢出 IO 包!System.out 和System.out 就是PrintStream 的实例! 蓝色的部分是 IO 字节流的主要组成部分,存在极强的对称关系。关于搭配使用的三对类补充一下:ObjectInputStream/ObjectOutputStream 和 DataInputStream/DataOutputStream 主要是要求写对象/数据和读对象/数据的次序要保持一致,否则轻则不能得到正确的数据,重则抛出异常(一般会如此); PipedInputStream/PipedOutputStream 在创建时一般就一起创建,调用它们的读写方法时会检查对方是否存在,或者关闭!道理极其简单――对方都不在了,怎么 交互啊! 3.43.43.43.4 字节流与字符流 从上面我们可以看出 IO 中的字节流是极其复杂的,存在大量的类,到目前为止还没有真正使用它们,使用它们应该也是极其复杂的吧!JDK1.1 后Sun 对IO库进行 了重大的改进。看到Reader 和Writer 类时,大多数人的第一个感觉(不要太相信感觉哦!感觉也许会欺骗你的!)就是它们是用来替换原来的 InputStream 和OutputStream 类。有新的类,干吗还使用旧的呢!?但实情并非如此。尽管 Sun 不建议使用原始的流库中的某些功能,但原来的流依然得到了保留,不仅为了保持向后兼容,主要原 因是新库不是旧库的替代,而是对旧库的增强。从以下两点可以明显地看出: (1) 在老式的类层次结构里加入了新的类,这表明 Sun 公司没有放弃老式流库的意图。 (2) 在许多情况下,新库中类的使用需要联合老结构中的类。为达到这个目的,需要使用一些“桥”类,如:InputStreamReader 将一个 InputStream 转换成 Reader; OutputStreamWriter 将一个 OutputStream 转换成 Writer。 那么Sun 为什么在 Java 1.1 里添加了 Reader 和Writer 层次,最重要的原因便是国际化(Internationalization――i18n)的需求。老式IO流层次结构只支持 8位字节流, 不能很好地控制 16位的Unicode 字符。Java 本身支持 Unicode,Sun 又一致吹嘘其支持 Unicode,因此有必要实现一个支持 Unicode 的流的层次结构,所以出现了 Reader 和Writer 层次,以提供对所有 IO 操作中的 Unicode 的支持。除此之外,新库也对速度进行了优化,可比旧库更快地运行。 8位的字节流和 16位的字符流的对应关系,可以从 ByteInputStream/ByteOutputStream 与CharArrayInputStream/CharArrayOutputStream 的对应关系中看出端倪。(还 没看出来啊!赶紧去看看 Java 的基本数据类型)。 因此在 Java 的IO 体系中存在字节流和字符流的对应关系。下面就看看字符流吧! 3.53.53.53.5 IO IO IO IO 中的输入字符流 下面是 IO中输入字符流的继承图。 • Reader • BufferedReader • LineNumberReader CharArrayReader FilterReader PushbackReader InputStreamReader FileReader PipedReader StringReader 在上面的关系图中可以看出:1.Reader 是所有的输入字符流的父类,它是一个抽象类。2.CharReader、StringReader 是两种基本的介质流,它们分别将 Char 数组、String 中读取数据。PipedReader 是从与其它线程共用的管道中读取数据。3. BufferedReader 很明显就是一个装饰器,它和其子类负责装饰其它 Reader 对象。4.FilterReader 是所 有自定义具体装饰流的父类,其子类 PushbackReader 对Reader 对象进行装饰,会增加一个行号。5.InputStreamReader 是一个连接字节流和字符流的桥梁,它将字节流转 变为字符流。FileReader 可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将 FileInputStream 转变为 Reader 的方法。我们可以从这个类中得到一定的 技巧。 Reader 中各个类的用途和使用方法基本和 InputStream 中的类使用一致。后面会有 Reader 与InputStream 的对应关系。 3.63.63.63.6 IO IO IO IO 中的输出字符流 下面是 IO中输出字符流的继承图。 • Writer • BufferedWriter CharArrayWriter FilterWriter OutputStreamWriter • FileWriter PipedWriter PrintWriter StringWriter 在上面的关系图中可以看出:1.Writer 是所有的输出字符流的父类,它是一个抽象类。2. CharArrayWriter、StringWriter 是两种基本的介质流,它们分别向 Char 数组、 String 中写入数据。PipedWriter 是向与其它线程共用的管道中写入数据, 3. BufferedWriter 是一个装饰器为 Writer 提供缓冲功能。4.PrintWriter 和PrintStream 极其类似, 功能和使用也非常相似。5.OutputStreamWriter 是OutputStream 到Writer 转换的桥梁,它的子类 FileWriter 其实就是一个实现此功能的具体类(具体可以研究一下 Source Code)。功能和使用和 OutputStream 极其类似,后面会有它们的对应图。 3.7 3.7 3.7 3.7 字符流的输入与输出的对应 下图为字符流的输入与输出的对应关系图: 对应关系和字节流的输入输出基本一致,不必多说了吧!在下面的源代码阅读部分会仔细研究一些! 3.8 3.8 3.8 3.8 字节流和字符流的对应 Java 的IO 中存在输入、输出的对应和字节流和字符流的对应,下面就看看字节流和字符流的对应吧! 3.8.1 3.8.1 3.8.1 3.8.1 输 入的 对应 输 入的 对应 输 入的 对应 输 入的 对应 下图是 IO 中字节输入流与字符输入流的对应图: 蓝色的表示对应的部分,红色的表示不对应的部分。至于为什么不对应还是你自己多看看源代码、多考虑考虑吧!还要强调一点就是即使对应,它们的继承关系也是 不太对应的。 3.8.2 3.8.2 3.8.2 3.8.2 输出的对应输出的对应输出的对应输出的对应 下图是 IO中字节输出流与字符输出流的对应图: 不多说了!等讲述了 Adapter 和Decorator 模式会基本明白 IO架构的!通过几个实例一般就可以使用了! 从InputStream InputStream InputStream InputStream 到ByteArrayInputStreamByteArrayInputStreamByteArrayInputStreamByteArrayInputStream 江苏 无锡 缪小东 本篇主要分析:1.如何将 byte 数组适配至 ByteArrayInputStream,对应与 IO部分的适配器模式;2.BufferedInputStream 的工作原理,对应于 IO的装饰器模式,会首 先研究 InputStream 和FilterInputStream 的源代码,同时会将要谈谈软件设计中的缓存相关的知识。后面专门一章分析 PipedInputStream 和PipedOutStream,简单谈谈管道 相关的知识,以及软件架构的想法。 1111 InputStreamInputStreamInputStreamInputStream InputStream 是输入字节流部分,装饰器模式的顶层类。主要规定了输入字节流的公共方法。 package java.io; public abstract class InputStream implements Closeable { private static final int SKIP_BUFFER_SIZE = 2048; //用于skip 方法,和 skipBuffer 相关 private static byte[] skipBuffer; // skipBuffer is initialized in skip(long), if needed. public abstract int read() throws IOException; //从输入流中读取下一个字节, //正常返回 0-255,到达文件的末尾返回-1 //在流中还有数据,但是没有读到时该方法会阻塞(block) //Java IO和New IO的区别就是阻塞流和非阻塞流 //抽象方法哦!不同的子类不同的实现哦! //将流中的数据读入放在 byte 数组的第 off 个位置先后的 len 个位置中 //放回值为放入字节的个数。 public int read(byte b[], int off, int len) throws IOException {// if (b == null) { throw new NullPointerException(); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; }//检查输入是否正常。一般情况下,检查输入是方法设计的第一步 int c = read(); //读取下一个字节 if (c == -1) { return -1; }//到达文件的末端返回-1 b[off] = (byte)c; //放回的字节 downcast int i = 1; //已经读取了一个字节 try { for (; i < len ; i++) {//最多读取 len 个字节,所以要循环 len 次 c = read(); //每次循环从流中读取一个字节 //由于read 方法阻塞, //所以read(byte[],int,int)也会阻塞 if (c == -1) { break; }//到达末尾,理所当然放回-1 b[off + i] = (byte)c; //读到就放入 byte 数组中 } } catch (IOException ee) {} return i; //上面这个部分其实还有一点比较重要,int i = 1;在循环的外围,或许你经常见到, //或许你只会在循环是才声明,为什么呢? //声明在外面,增大了变量的生存周期(在循环外面),所以后面可以 return 返回 //极其一般的想法。在类成员变量生命周期中使用同样的理念。 //在软件设计中,类和类的关系中也是一样的。 }//这个方法在利用抽象方法 read,某种意义上简单的 Templete 模式。 public int read(byte b[]) throws IOException { return read(b, 0, b.length); }//利用上面的方法 read(byte[] b) public long skip(long n) throws IOException { long remaining = n; //方法内部使用的、表示要跳过的字节数目, //使用它完成一系列字节读取的循环 int nr; if (skipBuffer == null) skipBuffer = new byte[SKIP_BUFFER_SIZE]; //初始化一个跳转的缓存 byte[] localSkipBuffer = skipBuffer; //本地化的跳转缓存 if (n <= 0) { return 0; }//检查输入参数,应该放在方法的开始 while (remaining > 0) {//一共要跳过 n个,每次跳过部分,循环 nr = read(localSkipBuffer, 0, (int) Math.min(SKIP_BUFFER_SIZE, remaining)); //利用上面的 read(byte[],int,int)方法尽量读取 n个字节 if (nr < 0) { break; }//读到流的末端,则返回 remaining -= nr; //没有完全读到需要的,则继续循环 } return n - remaining;//返回时要么全部读完,要么因为到达文件末端,读取了部分 } public int available() throws IOException {//查询流中还有多少可以读取的字节 return 0; } //该方法不会 block。在java 中抽象类方法的实现一般有以下几种方式: //1.抛出异常(java.util);2.“弱”实现。象上面这种。子类在必要的时候覆盖它。 //3.“空”实现。下面有例子。 public void close() throws IOException {} //关闭当前流、同时释放与此流相关的资源 public synchronized void mark(int readlimit) {} //在当前位置对流进行标记,必要的时候可以使用 reset 方法返回。 //markSupport 可以查询当前流是否支持 mark public synchronized void reset() throws IOException { throw new IOException("mark/reset not supported"); } //对mark 过的流进行复位。只有当流支持 mark 时才可以使用此方法。 //看看mark、available 和reset 方法。体会为什么?! public boolean markSupported() {//查询是否支持 mark return false; }//绝大部分不支持,因此提供默认实现,返回 false。子类有需要可以覆盖。 } 2222 FilterInputStreamFilterInputStreamFilterInputStreamFilterInputStream 这是字节输入流部分装饰器模式的核心。是我们在装饰器模式中的 Decorator 对象,主要完成对其它流装饰的基本功能。下面是它的源代码: package java.io; //该类对被装饰的流进行基本的包裹。不增加额外的功能。 //客户在需要的时候可以覆盖相应的方法。具体覆盖可以在 ByteInputStream 中看到! public class FilterInputStream extends InputStream { protected volatile InputStream in; //将要被装饰的字节输入流 protected FilterInputStream(InputStream in) {//通过构造方法传入此被装饰的流 this.in = in; } //装饰器的代码特征:被装饰的对象一般是装饰器的成员变量 //上面几行可以看出。 //下面这些方法,完成最小的装饰――0装饰,只是调用被装饰流的方法而已 public int read() throws IOException { return in.read(); } public int read(byte b[]) throws IOException { return read(b, 0, b.length); } public int read(byte b[], int off, int len) throws IOException { return in.read(b, off, len); } public long skip(long n) throws IOException { return in.skip(n); } public int available() throws IOException { return in.available(); } public void close() throws IOException { in.close(); } public synchronized void mark(int readlimit) { in.mark(readlimit); } public synchronized void reset() throws IOException { in.reset(); } public boolean markSupported() { return in.markSupported(); } //以上的方法,都是通过调用被装饰对象 in完成的。没有添加任何额外功能 //装饰器模式中的 Decorator 对象,不增加被装饰对象的功能。 //它是装饰器模式中的核心。更多关于装饰器模式的理论请阅读博客中的文章。 } 以上分析了所有字节输入流的公共父类 InputStream 和装饰器类 FilterInputStream 类。他们是装饰器模式中两个重要的类。更多细节请阅读博客中装饰器模式的文章。 下面将讲解一个具体的流 ByteArrayInputStream,不过它是采用适配器设计模式。 3333 ByteArray ByteArray ByteArray ByteArray 到ByteArrayInputStream ByteArrayInputStream ByteArrayInputStream ByteArrayInputStream 的适配 // ByteArrayInputStream 内部有一个 byte 类型的 buffer。 //很典型的适配器模式的应用――将byte 数组适配流的接口。 //下面是源代码分析: package java.io; public class ByteArrayInputStream extends InputStream { protected byte buf[]; //内部的 buffer,一般通过构造器输入 protected int pos; //当前位置的 cursor。从0至byte 数组的长度。 //byte[pos]就是read 方法读取的字节 protected int mark = 0; //mark 的位置。 protected int count; //流中字节的数目。不一定与 byte[]的长度一致??? public ByteArrayInputStream(byte buf[]) {//从一个 byte[]创建一个 ByteArrayInputStream this.buf = buf; //初始化流中的各个成员变量 this.pos = 0; this.count = buf.length; //count 就等于 buf.length } public ByteArrayInputStream(byte buf[], int offset, int length) {//构造器 this.buf = buf; this.pos = offset; //与上面不同 this.count = Math.min(offset + length, buf.length); this.mark = offset; //与上面不同 } public synchronized int read() {//从流中读取下一个字节 return (pos < count) ?(buf[pos++] & 0xff) :-1; //返回下一个位置的字节 //流中没有数据则返回-1 } //下面这个方法很有意思!从 InputStream 中可以看出其提供了该方法的实现。 //为什么 ByteArrayInputStream 要覆盖此方法呢? //同样的我们在 Java Collections Framework 中可以看到: //AbstractCollection 利用iterator 实现了 Collecion 接口的很多方法。但是, //在ArrayList 中却有很多被子类覆盖了。为什么如此呢?? public synchronized int read(byte b[], int off, int len) { if (b == null) {//首先检查输入参数的状态是否正确 throw new NullPointerException(); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } if (pos >= count) { return -1; } if (pos + len > count) { len = count - pos; } if (len <= 0) { return 0; } System.arraycopy(buf, pos, b, off, len); //java 中提供数据复制的方法 pos += len; return len; } //出于速度的原因!他们都用到 System.arraycopy 方法。想想为什么? //某些时候,父类不能完全实现子类的功能,父类的实现一般比较通用。 //当子类有更有效的方法时,我们会覆盖这些方法。这样可是不太 OO的哦! //下面这个方法,在 InputStream 中也已经实现了。 //但是当时是通过将字节读入一个 buffer 中实现的,好像效率低了一点。 //看看下面这段代码,是否极其简单呢?! public synchronized long skip(long n) { if (pos + n > count) { n = count - pos; }//当前位置,可以跳跃的字节数目 if (n < 0) { return 0; }//小于0,则不可以跳跃 pos += n; //跳跃后,当前位置变化 return n; }//比InputStream 中的方法简单、高效吧! public synchronized int available() { return count - pos; } //查询流中还有多少字节没有读取。 //在我们的 ByteArrayInputStream 中就是当前位置以后字节的数目。 public boolean markSupported() { return true; }//ByteArrayInputStream 支持mark 所以返回 true public void mark(int readAheadLimit) { mark = pos; } //在流中当前位置 mark。 //在我们的 ByteArrayInputStream 中就是将当前位置赋给 mark 变量。 //读取流中的字节就是读取字节数组中当前位置向后的的字节。 public synchronized void reset() { pos = mark; } //重置流。即回到 mark 的位置。 public void close() throws IOException {} //关闭ByteArrayInputStream 不会产生任何动作。为什么?仔细考虑吧!! } 上面我们分 3小节讲了装饰器模式中的公共父类(对应于输入字节流的 InputStream)、Decorator(对应于输入字节流的 FilterInputStream)和基本被装饰对象(对应 于输入字节流的媒体字节流)。下面我们就要讲述装饰器模式中的具体的包装器(对应于输入字节流的包装器流)。 4444 BufferedInputStreamBufferedInputStreamBufferedInputStreamBufferedInputStream 4.1 4.1 4.1 4.1 原 理及 其在 软件 硬件 中的 应用 原 理及 其在 软件 硬件 中的 应用 原 理及 其在 软件 硬件 中的 应用 原 理及 其在 软件 硬件 中的 应用 1.read――read(byte[] ,int , int) 2.BufferedInputStream 3.《由一个简单的程序谈起》 4. Cache 5.Pool 6.Spling Printer (最近比较忙,不讲了!) 4.24.24.24.2 BufferedInputStream BufferedInputStream BufferedInputStream BufferedInputStream 源 代码 分析 源 代码 分析 源 代码 分析 源 代码 分析 package java.io; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; //该类主要完成对被包装流,加上一个缓存的功能 public class BufferedInputStream extends FilterInputStream { private static int defaultBufferSize = 8192; //默认缓存的大小 protected volatile byte buf[]; //内部的缓存 protected int count; //buffer 的大小 protected int pos; //buffer 中cursor 的位置 protected int markpos = -1; //mark 的位置 protected int marklimit; //mark 的范围 //原子性更新。和一致性编程相关 private static final AtomicReferenceFieldUpdater bufUpdater = AtomicReferenceFieldUpdater.newUpdater (BufferedInputStream.class, byte[].class, "buf"); private InputStream getInIfOpen() throws IOException {//检查输入流是否关闭,同时返回被包装流 InputStream input = in; if (input == null) throw new IOException("Stream closed"); return input; } private byte[] getBufIfOpen() throws IOException {//检查buffer 的状态,同时返回缓存 byte[] buffer = buf; if (buffer == null) throw new IOException("Stream closed"); //不太可能发生的状态 return buffer; } public BufferedInputStream(InputStream in) {//构造器 this(in, defaultBufferSize); //指定默认长度的 buffer } public BufferedInputStream(InputStream in, int size) {//构造器 super(in); if (size <= 0) {//检查输入参数 throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size]; //创建指定长度的 buffer } //从流中读取数据,填充如缓存中。 private void fill() throws IOException { byte[] buffer = getBufIfOpen(); //得到buffer if (markpos < 0) pos = 0; //mark 位置小于 0,此时 pos 为0 else if (pos >= buffer.length) //pos 大于buffer 的长度 if (markpos > 0) { int sz = pos - markpos; // System.arraycopy(buffer, markpos, buffer, 0, sz); pos = sz; markpos = 0; } else if (buffer.length >= marklimit) {//buffer 的长度大于 marklimit 时,mark 失效 markpos = -1; // pos = 0; //丢弃buffer 中的内容 } else {//buffer 的长度小于 marklimit 时对buffer 扩容 int nsz = pos * 2; if (nsz > marklimit) nsz = marklimit;//扩容为原来的 2倍,太大则为 marklimit 大小 byte nbuf[] = new byte[nsz]; System.arraycopy(buffer, 0, nbuf, 0, pos); //将buffer 中的字节拷贝如扩容后的 buf 中 if (!bufUpdater.compareAndSet(this, buffer, nbuf)) { //在buffer 在被操作时,不能取代此 buffer throw new IOException("Stream closed"); } buffer = nbuf; //将新buf 赋值给 buffer } count = pos; int n = getInIfOpen().read(buffer, pos, buffer.length - pos); if (n > 0) count = n + pos; } public synchronized int read() throws IOException {//读取下一个字节 if (pos >= count) {//到达buffer 的末端 fill(); //就从流中读取数据,填充 buffer if (pos >= count) return -1; //读过一次,没有数据则返回-1 } return getBufIfOpen()[pos++] & 0xff; //返回buffer 中下一个位置的字节 } private int read1(byte[] b, int off, int len) throws IOException {//将数据从流中读入 buffer 中 int avail = count - pos; //buffer 中还剩的可读字符 if (avail <= 0) {//buffer 中没有可以读取的数据时 if (len >= getBufIfOpen().length && markpos < 0) {//将输入流中的字节读入 b中 return getInIfOpen().read(b, off, len); } fill(); //填充 avail = count - pos; if (avail <= 0) return -1; } int cnt = (avail < len) ? avail : len; //从流中读取后,检查可以读取的数目 System.arraycopy(getBufIfOpen(), pos, b, off, cnt); //将当前 buffer 中的字节放入 b的末端 pos += cnt; return cnt; } public synchronized int read(byte b[], int off, int len)throws IOException { getBufIfOpen(); // 检查buffer 是否open if ((off | len | (off + len) | (b.length -(off + len))) < 0) {//检查输入参数是否正确 throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } int n = 0; for (;;){ int nread = read1(b, off + n, len - n); if (nread <= 0) return (n == 0) ? nread : n; n += nread; if (n >= len) return n; // if not closed but no bytes available, return InputStream input = in; if (input != null && input.available() <= 0) return n; } } public synchronized long skip(long n) throws IOException { getBufIfOpen(); // 检查buffer 是否关闭 if (n <= 0) { return 0; }//检查输入参数是否正确 long avail = count - pos; //buffered 中可以读取字节的数目 if (avail <= 0) {//可以读取的小于 0,则从流中读取 if (markpos <0) return getInIfOpen().skip(n); //mark 小于0,则mark 在流中 fill(); // 从流中读取数据,填充缓冲区。 avail = count - pos; //可以读的取字节为 buffer 的容量减当前位置 if (avail <= 0) return 0; } long skipped = (avail < n) ? avail : n; pos += skipped; //当前位置改变 return skipped; } public synchronized int available() throws IOException { return getInIfOpen().available() + (count - pos); } //该方法不会 block!返回流中可以读取的字节的数目。 //该方法的返回值为缓存中的可读字节数目加流中可读字节数目的和 public synchronized void mark(int readlimit) {//当前位置处为 mark 位置 marklimit = readlimit; markpos = pos; } public synchronized void reset() throws IOException { getBufIfOpen(); // 缓冲去关闭了,肯定就抛出异常!程序设计中经常的手段 if (markpos < 0) throw new IOException("Resetting to invalid mark"); pos = markpos; } public boolean markSupported() {//该流和 ByteArrayInputStream 一样都支持 mark return true; } //关闭当前流同时释放相应的系统资源。 public void close() throws IOException { byte[] buffer; while ((buffer = buf) != null) { if (bufUpdater.compareAndSet(this, buffer, null)) { InputStream input = in; in = null; if (input != null) input.close(); return; } // Else retry in case a new buf was CASed in fill() } } } 从PipedInputStream/PipedOutputStream PipedInputStream/PipedOutputStream PipedInputStream/PipedOutputStream PipedInputStream/PipedOutputStream 谈起 江苏 无锡 缪小东 本篇主要从分析 PipeInputStrem 和PipedOutputStream 谈起。谈及软件设计的变化,以及如何将软件拆分、组合,适配…… 1111 源代码分析 下面将详细分析 PipedInputStream 和PipedOutputStream 的源代码。 1.11.11.11.1 PipedInputStreamPipedInputStreamPipedInputStreamPipedInputStream package java.io; //PipedInputStream 必须和 PipedOutputStream 联合使用。即必须连接输入部分。 //其原理为:PipedInputStream 内部有一个 Buffer, //PipedInputStream 可以使用 InputStream 的方法读取其 Buffer 中的字节。 //PipedInputStream 中Buffer 中的字节是 PipedOutputStream 调用PipedInputStream 的方法放入的。 public class PipedInputStream extends InputStream { boolean closedByWriter = false; //标识有读取方或写入方关闭 volatile boolean closedByReader = false; boolean connected = false; //是否建立连接 Thread readSide; //标识哪个线程 Thread writeSide; protected static final int PIPE_SIZE = 1024; //缓冲区的默认大小 protected byte buffer[] = new byte[PIPE_SIZE]; //缓冲区 protected int in = -1; //下一个写入字节的位置。0代表空,in==out 代表满 protected int out = 0; //下一个读取字节的位置 public PipedInputStream(PipedOutputStream src) throws IOException {//给定源的输入流 connect(src); } public PipedInputStream() {}//默认构造器,下部一定要 connect 源 public void connect(PipedOutputStream src) throws IOException {//连接输入源 src.connect(this); //调用源的 connect 方法连接当前对象 } protected synchronized void receive(int b) throws IOException {//只被PipedOuputStream 调用 checkStateForReceive(); //检查状态,写入 writeSide = Thread.currentThread(); //永远是 PipedOuputStream if (in == out) awaitSpace(); //输入和输出相等,等待空间 if (in < 0) { in = 0; out = 0; } buffer[in++] = (byte)(b & 0xFF); //放入buffer 相应的位置 if (in >= buffer.length) { in = 0; }//in 为0表示buffer 已空 } synchronized void receive(byte b[], int off, int len) throws IOException { checkStateForReceive(); writeSide = Thread.currentThread(); //从PipedOutputStream 可以看出 int bytesToTransfer = len; while (bytesToTransfer > 0) { if (in == out) awaitSpace(); //满了,会通知读取的;空会通知写入 int nextTransferAmount = 0; if (out < in) { nextTransferAmount = buffer.length - in; } else if (in < out) { if (in == -1) { in = out = 0; nextTransferAmount = buffer.length - in; } else { nextTransferAmount = out - in; } } if (nextTransferAmount > bytesToTransfer) nextTransferAmount = bytesToTransfer; assert(nextTransferAmount > 0); System.arraycopy(b, off, buffer, in, nextTransferAmount); bytesToTransfer -= nextTransferAmount; off += nextTransferAmount; in += nextTransferAmount; if (in >= buffer.length) { in = 0; } } } private void checkStateForReceive() throws IOException {//检查当前状态,等待输入 if (!connected) { throw new IOException("Pipe not connected"); } else if (closedByWriter || closedByReader) { throw new IOException("Pipe closed"); } else if (readSide != null &&!readSide.isAlive()) { throw new IOException("Read end dead"); } } private void awaitSpace() throws IOException {//Buffer 已满,等待一段时间 while (in == out) {//in==out 表示满了,没有空间 checkStateForReceive(); //检查接受端的状态 notifyAll(); //通知读取端 try { wait(1000); } catch (InterruptedException ex) { throw new java.io.InterruptedIOException(); } } } synchronized void receivedLast() {//通知所有等待的线程()已经接受到最后的字节 closedByWriter = true; // notifyAll(); } public synchronized int read() throws IOException { if (!connected) {//检查一些内部状态 throw new IOException("Pipe not connected"); } else if (closedByReader) { throw new IOException("Pipe closed"); } else if (writeSide != null &&!writeSide.isAlive()&& !closedByWriter &&(in < 0)) { throw new IOException("Write end dead"); } readSide = Thread.currentThread(); //当前线程读取 int trials = 2; //重复两次???? while (in < 0) { if (closedByWriter) { return -1; }//输入断关闭返回-1 if ((writeSide != null) &&(!writeSide.isAlive()) &&(--trials < 0)) {//状态错误 throw new IOException("Pipe broken"); } notifyAll(); // 空了,通知写入端可以写入 try { wait(1000); } catch (InterruptedException ex) { throw new java.io.InterruptedIOException(); } } int ret = buffer[out++] & 0xFF; // if (out >= buffer.length) { out = 0; } if (in == out) { in = -1; }//没有任何字节 return ret; } public synchronized int read(byte b[], int off, int len) throws IOException { if (b == null) {//检查输入参数的正确性 throw new NullPointerException(); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } int c = read(); //读取下一个 if (c < 0) { return -1; }//已经到达末尾了,返回-1 b[off] = (byte) c; //放入外部 buffer 中 int rlen = 1; //return-len while ((in >= 0) &&(--len > 0)) {//下一个 in存在,且没有到达 len b[off + rlen] = buffer[out++]; //依次放入外部 buffer rlen++; if (out >= buffer.length) { out = 0; }//读到buffer 的末尾,返回头部 if (in == out) { in = -1; }//读、写位置一致时,表示没有数据 } return rlen; //返回填充的长度 } public synchronized int available() throws IOException {//返回还有多少字节可以读取 if(in < 0) return 0; //到达末端,没有字节 else if(in == out) return buffer.length; //写入的和读出的一致,表示满 else if (in > out) return in - out; //写入的大于读出 else return in + buffer.length - out; //写入的小于读出的 } public void close() throws IOException {//关闭当前流,同时释放与其相关的资源 closedByReader = true; //表示由输入流关闭 synchronized (this) { in = -1; }//同步化当前对象,in为-1 } } 1.21.21.21.2 PipedOutputStreamPipedOutputStreamPipedOutputStreamPipedOutputStream // PipedOutputStream 一般必须和一个 PipedInputStream 连接。共同构成一个 pipe。 //它们的职能是: package java.io; import java.io.*; public class PipedOutputStream extends OutputStream { private PipedInputStream sink; //包含一个 PipedInputStream public PipedOutputStream(PipedInputStream snk)throws IOException {//带有目的地的构造器 connect(snk); } public PipedOutputStream() {}//默认构造器,必须使用下面的 connect 方法连接 public synchronized void connect(PipedInputStream snk) throws IOException { if (snk == null) {//检查输入参数的正确性 throw new NullPointerException(); } else if (sink != null || snk.connected) { throw new IOException("Already connected"); } sink = snk; //一系列初始化工作 snk.in = -1; snk.out = 0; snk.connected = true; } public void write(int b) throws IOException {//向流中写入数据 if (sink == null) { throw new IOException("Pipe not connected"); } sink.receive(b); //本质上是,调用 PipedInputStream 的receive 方法接受此字节 } public void write(byte b[], int off, int len) throws IOException { if (sink == null) {//首先检查输入参数的正确性 throw new IOException("Pipe not connected"); } else if (b == null) { throw new NullPointerException(); } else if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return; } sink.receive(b, off, len); //调用PipedInputStream 的receive 方法接受 } public synchronized void flush() throws IOException {//flush 输出流 if (sink != null) { synchronized (sink) { sink.notifyAll(); }//本质是通知输入流,可以读取 } } public void close() throws IOException {//关闭流同时释放相关资源 if (sink != null) { sink.receivedLast(); } } } 2222 Buffer Buffer Buffer Buffer 的状态 上图是 PipedInputStream 中缓存的状态图。在程序中我们利用了 byte 数组,循环地向其中写入数据,写入有一个 cursor(in),读出也有一个 cursor(out)。上图表 示in 和out 不同位置时,buffer 中的各个位置的状态。蓝色的代表可以读取的字节。白色的表示此位置没有字节,或者此位置已经被 PipedInputStream 读取了。 3333 交互简图 下图是从源代码部分转换过来的关于 PipedInputStream 和PipedOutputStream 的交互图。 从图中可以看出: 1. 整个PipedInputStream 是这对管道的核心。管道本身是一个 byte 的数组。 2. PipedOutputStream 对象通过 Delegate 方法复用 PipedInputStream,同时屏蔽了其中的读取的方法,我们仅仅可以构造 PipedOutputStream 对象。(从这一点可以看出 Delegate 复用比继承复用的优越性了!)从设计模式的角度更象 Adapter――PipedInputStream 本身提供读取和写入的功能,将写入的功能适配到 OutputStream,就成 为一个 PipedOutputStream。这样就形成一个类,适配后形成两种功能的类。 3. 调用PipedOutputStream 的连接方法实际就是调用 PipedInputStream 的连接方法。 4. 调用PipedOutputStream 的写相关的方法实际就是调用 PipedInputStream 的对应方法。 以上也是一种适配,将管道的概念适配到流的概念,同时将两者的职能分开。 4444 将Chanel Chanel Chanel Chanel 放入PipedOutputStreamPipedOutputStreamPipedOutputStreamPipedOutputStream 上面的例子中,Chanel 放在PipedInputStream 中,我们仔细思考后可以顺理成章地将其 Chanel 放入PipedOutputStream 中。请注意 synchronized 方法是得到哪个字节 流的锁!! 5555 Chanel Chanel Chanel Chanel 移出的一个例子 在上面两个例子中 Buffer 要么在写入对象的内部,要么在读取对象的内部。主要通过适配该对象的方法,达到自己的需求而已。下面是一个一般的例子――将Chanel 移出,Chanel 提供了写入与读取的功能。这也完全合乎 OO的“Single Responsibility Protocol――SRP”。输入部分使用 Delegate 复用此 Chanel,将其适配至 InputStream 和OutputStream。下面是简单的 Source code。 //PipedChanel.java import java.io.IOException ; public class PipedChanel { protected static final int PIPE_SIZE = 1024; protected byte buffer[] = new byte[PIPE_SIZE]; protected int in = -1; protected int out = 0; public PipedChanel(){ } public PipedChanel(int size){ buffer = new byte[size] ; } public synchronized int read() throws IOException {} public synchronized int read(byte b[], int off, int len) throws IOException {} public synchronized int available() throws IOException {} public synchronized void close() throws IOException {} public synchronized void write(int b) throws IOException {} public synchronized void write(byte b[]) throws IOException {} public synchronized void write(byte b[], int off, int len) throws IOException {} public synchronized void flush() throws IOException {} public void waitWhileFull(){ }//当Chanel 已经满了,写线程等待 public void waitWhileEmpty{ }//当Chanel 为空,读取线程等待 //以上是两个操作 Chanel 时的状态相关的方法。 //是一致性编程部分,典型的设计模式。 //这两个方法,包含在对应读或写方法的最前面。 } // PipedChanelInputStream.java import java.io.*; public class PipedChanelInputStream extends InputStream { private PipedChanel chanel ; public PipedChanelInputStream(PipedChanel chanel){ this.chanel = chanel ; } public int read() throws IOException { return chanel.read(); } public int read(byte b[], int off, int len) throws IOException { return chanel.read(b,off,len); } public int available() throws IOException { return chanel.available(); } public void close() throws IOException { chanel.close(); } } // PipedChanelOutputStream.java import java.io.*; public class PipedChanelOutputStream extends OutputStream { private PipedChanel chanel ; public PipedChanelOutputStream(PipedChanel chanel){ this.chanel = chanel ; } public synchronized void write(int b) throws IOException { chanel.write(b); } public synchronized void write(byte b[]) throws IOException { chanel.write(b); } public synchronized void write(byte b[], int off, int len) throws IOException { chanel.write(b,off,len); } public synchronized void flush() throws IOException { chanel.flush(); } public synchronized void close() throws IOException { chanel.close(); } } 很简单的例子。我们可以体会适配器模式,可以体会软件设计的灵活性…… 上面的关于 PipedInputStream 和PipedOutputStream 的例子,本质上是对一个 Chanel 的几个不同的适配。Chanel 作为一种编程模式,在软件设计中有极其广泛的应用。 下面一节是 JMS 的简洁阐述! 以上的例子其实是一个典型的使用适配器。
还剩39页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 6 人已下载

下载pdf

pdf贡献者

boyika

贡献于2013-08-23

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf