Java IO完全总结(转载) --- 重点在源码分析
转载自https://blog.csdn.net/baobeisimple/article/details/1713797
个人认为前面对于输入输出流(超类)的 继承结构,仅仅作为参考知识了解,如果想了解IO细节, 重点看源码。
Java 8:活好水多——Java 的新IO (nio)
inputStream 的read方法是 abstract 因为不同的 输入流 对于 来源的解读方式都是不同的。例如fileInputStream 从文件中read() 一个二进制码,byteInputStream从 btye[] 中读取 .... filterInputStream 是一种装饰,它可以装饰其他的输入流,然后加以过滤。
in.read() 就是从 一个方式中读取一个字符。
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
|
跟踪输入流中的行号。可以调用getLineNumber( )和 setLineNumber(int)方法得到和设置行号。
|
利用一个InputStream构造。
|
紧紧增加一个行号。可以象使用其它InputStream一样使用。
|
||
PushbackInputStream
|
可以在读取最后一个byte 后将其放回到缓存中。
|
利用一个InputStream构造。
|
一般仅仅会在设计compiler的scanner 时会用到这个类。在我们的java语言的编译器中使用它。很多程序员可能一辈子都不需要。
|
- OutputStream
- ByteArrayOutputStream
- FileOutputStream
- FilterOutputStream
- BufferedOutputStream
- DataOutputStream
- PrintStream
- ObjectOutputStream
- PipedOutputStream
类
|
功能
|
如何构造
|
怎样使用
|
||
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.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.5 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.6 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.8字节流和字符流的对应
Java的IO中存在输入、输出的对应和字节流和字符流的对应,下面就看看字节流和字符流的对应吧!
3.8.1输入的对应
下图是IO中字节输入流与字符输入流的对应图:
蓝色的表示对应的部分,红色的表示不对应的部分。至于为什么不对应还是你自己多看看源代码、多考虑考虑吧!还要强调一点就是即使对应,它们的继承关系也是不太对应的。
3.8.2输出的对应
下图是IO中字节输出流与字符输出流的对应图:
不多说了!等讲述了Adapter和Decorator模式会基本明白IO架构的!通过几个实例一般就可以使用了!
从InputStream到ByteArrayInputStream
江苏 无锡 缪小东
本篇主要分析:1.如何将byte数组适配至ByteArrayInputStream,对应与IO部分的适配器模式;2.BufferedInputStream的工作原理,对应于IO的装饰器模式,会首先研究InputStream和FilterInputStream的源代码,同时会将要谈谈软件设计中的缓存相关的知识。后面专门一章分析PipedInputStream和PipedOutStream,简单谈谈管道相关的知识,以及软件架构的想法。
1 InputStream
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; } //到达文件的末端返回-1b[off] = (byte)c; //放回的字节downcastint i = 1; //已经读取了一个字节try {for (; i < len ; i++) { //最多读取len个字节,所以要循环len次c = read(); //每次循环从流中读取一个字节//由于read方法阻塞,//所以read(byte[],int,int)也会阻塞if (c == -1) { break; } //到达末尾,理所当然放回-1b[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可以查询当前流是否支持markpublic synchronized void reset() throws IOException {throw new IOException("mark/reset not supported");}//对mark过的流进行复位。只有当流支持mark时才可以使用此方法。//看看mark、available和reset方法。体会为什么?!public boolean markSupported() { //查询是否支持markreturn false;} //绝大部分不支持,因此提供默认实现,返回false。子类有需要可以覆盖。}
2 FilterInputStream
这是字节输入流部分装饰器模式的核心。是我们在装饰器模式中的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,不过它是采用适配器设计模式。
3 ByteArray到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[]创建一个ByteArrayInputStreamthis.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所以返回truepublic 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)和基本被装饰对象(对应于输入字节流的媒体字节流)。下面我们就要讲述装饰器模式中的具体的包装器(对应于输入字节流的包装器流)。
4 BufferedInputStream
4.1原理及其在软件硬件中的应用
1.read――read(byte[] ,int , int)
2.BufferedInputStream
3.《由一个简单的程序谈起》
4. Cache
5.Pool
6.Spling Printer
(最近比较忙,不讲了!)
4.2 BufferedInputStream源代码分析
bufferedInputStream 的 fill() 函数 旨在扩容并且从in.read()中读入新的内容,进行一次磁盘IO,(缓存大小) 。
bufferinputStream 是一种特殊的机制,它一次向buf中写入8192长度内容,这样相当于一次磁盘IO就可以写入足够内容,不同想其他一样多次进行磁盘IO浪费效率,毕竟read() 也是 阻塞的 极大地浪费效率。
所以在bufferinputstream中的read() 也会调用fill() 因为buf内容中 pos > count .read1()只是有多少读多少,read()是贪婪读,直至in中内容available为false;
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 finalAtomicReferenceFieldUpdater<BufferedInputStream, byte[]> 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}// fill() 函数 旨在扩容并且从in.read()中读入新的内容,进行一次磁盘IO。(缓存大小)
//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); //System.arraycopy()使用native方法,所以效率极高
//
// 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;
//
//}
/*** Fills the buffer with more data, taking into account* shuffling and other tricks for dealing with marks.* Assumes that it is being called by a synchronized method.* This method also assumes that all data has already been read in,* hence pos > count.*/private void fill() throws IOException {byte[] buffer = getBufIfOpen();if (markpos < 0)pos = 0; /* no mark: throw away the buffer */else if (pos >= buffer.length) /* no room left in buffer */if (markpos > 0) { /* can throw away early part of the buffer */int sz = pos - markpos;System.arraycopy(buffer, markpos, buffer, 0, sz);pos = sz;markpos = 0;} else if (buffer.length >= marklimit) {markpos = -1; /* buffer got too big, invalidate mark */pos = 0; /* drop buffer contents */} else if (buffer.length >= MAX_BUFFER_SIZE) {throw new OutOfMemoryError("Required array size too large");} else { /* grow buffer */int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?pos * 2 : MAX_BUFFER_SIZE;if (nsz > marklimit)nsz = marklimit;byte nbuf[] = new byte[nsz];System.arraycopy(buffer, 0, nbuf, 0, pos);if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {// Can't replace buf if there was an async close.// Note: This would need to be changed if fill()// is ever made accessible to multiple threads.// But for now, the only way CAS can fail is via close.// assert buf == null;throw new IOException("Stream closed");}buffer = nbuf;}count = pos;int n = getInIfOpen().read(buffer, pos, buffer.length - pos);if (n > 0)count = n + pos;}
从PipedInputStream/PipedOutputStream谈起
江苏 无锡 缪小东
本篇主要从分析PipeInputStrem和PipedOutputStream谈起。谈及软件设计的变化,以及如何将软件拆分、组合,适配……
1 源代码分析
下面将详细分析PipedInputStream和PipedOutputStream的源代码。
1.1 PipedInputStream
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(); //永远是PipedOuputStreamif (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; } //输入断关闭返回-1if ((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; } //已经到达末尾了,返回-1b[off] = (byte) c; //放入外部buffer中int rlen = 1; //return-lenwhile ((in >= 0) && (--len > 0)) { //下一个in存在,且没有到达lenb[off + rlen] = buffer[out++]; //依次放入外部bufferrlen++;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; //写入的大于读出elsereturn in + buffer.length - out; //写入的小于读出的}public void close() throws IOException { //关闭当前流,同时释放与其相关的资源closedByReader = true; //表示由输入流关闭synchronized (this) { in = -1; } //同步化当前对象,in为-1}}1.2 PipedOutputStream
// PipedOutputStream一般必须和一个PipedInputStream连接。共同构成一个pipe。//它们的职能是:package java.io;import java.io.*;public class PipedOutputStream extends OutputStream {private PipedInputStream sink; //包含一个PipedInputStreampublic 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(); }}}
2 Buffer的状态
3 交互简图
下图是从源代码部分转换过来的关于PipedInputStream和PipedOutputStream的交互图。
从图中可以看出:
1. 整个PipedInputStream是这对管道的核心。管道本身是一个byte的数组。
2. PipedOutputStream对象通过Delegate方法复用PipedInputStream,同时屏蔽了其中的读取的方法,我们仅仅可以构造PipedOutputStream对象。(从这一点可以看出Delegate复用比继承复用的优越性了!)从设计模式的角度更象Adapter――PipedInputStream本身提供读取和写入的功能,将写入的功能适配到OutputStream,就成为一个PipedOutputStream。这样就形成一个类,适配后形成两种功能的类。
3. 调用PipedOutputStream的连接方法实际就是调用PipedInputStream的连接方法。
4. 调用PipedOutputStream的写相关的方法实际就是调用PipedInputStream的对应方法。
以上也是一种适配,将管道的概念适配到流的概念,同时将两者的职能分开。
4 将Chanel放入PipedOutputStream
上面的例子中,Chanel放在PipedInputStream中,我们仔细思考后可以顺理成章地将其Chanel放入PipedOutputStream中。请注意synchronized方法是得到哪个字节流的锁!!
5 Chanel移出的一个例子
在上面两个例子中Buffer要么在写入对象的内部,要么在读取对象的内部。主要通过适配该对象的方法,达到自己的需求而已。下面是一个一般的例子――将Chanel移出,Chanel提供了写入与读取的功能。这也完全合乎OO的“Single Responsibility Protocol――SRP”。输入部分使用Delegate复用此Chanel,将其适配至InputStream和OutputStream。下面是简单的Source code。
//PipedChanel.javaimport 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.javaimport 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.javaimport 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的简洁阐述!
以上的例子其实是一个典型的使用适配器。
6 JMS的架构
JMS为J2EE部分的面向消息中间件的API。JMS的Queue、Topic某种意义上就是我们上面Chanel移到网络的其它一段――服务器上的一个例子。同时该Chanel得到了很多强化。如:1.支持交易;2.支持持久化……
在J2EE中JMS是一个比较重要的方向,大型的企业应用中都会使用。不过J2EE中给出了其API,背后的理念还是相当丰富的!(具体细节以后会有相关文章!!唉,还是因为忙!!)
Java IO完全总结(转载) --- 重点在源码分析相关推荐
- java中的==、equals()、hashCode()源码分析(转载)
在java编程或者面试中经常会遇到 == .equals()的比较.自己看了看源码,结合实际的编程总结一下. 1. == java中的==是比较两个对象在JVM中的地址.比较好理解.看下面的代码: ...
- Java的三种代理模式完整源码分析
Java的三种代理模式&完整源码分析 Java的三种代理模式&完整源码分析 参考资料: 博客园-Java的三种代理模式 简书-JDK动态代理-超详细源码分析 [博客园-WeakCach ...
- 【Java并发编程】16、ReentrantReadWriteLock源码分析
一.前言 在分析了锁框架的其他类之后,下面进入锁框架中最后一个类ReentrantReadWriteLock的分析,它表示可重入读写锁,ReentrantReadWriteLock中包含了两种锁,读锁 ...
- java集合(6):TreeMap源码分析(jdk1.8)
前言 TreeMap的基本概念: TreeMap集合是基于红黑树(Red-Black tree)的 NavigableMap实现.该集合最重要的特点就是可排序,该映射根据其键的自然顺序进行排序,或者根 ...
- 这篇文章绝对让你深刻理解java类的加载以及ClassLoader源码分析
前言 package com.jvm.classloader;class Father2{public static String strFather="HelloJVM_Father&qu ...
- Java 集合系列(四)—— ListIterator 源码分析
以脑图的形式来展示Java集合知识,让零碎知识点形成体系 Iterator 对比 Iterator(迭代器)是一种设计模式,是一个对象,用于遍历集合中的所有元素. Iterator 包含四个方 ...
- 转载-FileZilla Server源码分析(1)
FileZilla Server源码分析(1) 分类: VC 2012-03-27 17:32 2363人阅读 评论(0) 收藏 举报 serversocketftp服务器usersockets工作 ...
- 转载:Docker源码分析(一):Docker架构
原文地址: http://www.infoq.com/cn/articles/docker-source-code-analysis-part1 作者:孙宏亮 1 背景 1.1 Docker简介 D ...
- java parseint 负号_java.lang.Integer#parseInt() 源码分析
Integer#parseInt() 是我们经常使用的一个函数, 是 Integer 类提供的一个静态工具方法, 其作用就是将字符串的数字转换为 int 类型. 一个更通用的 parseInt() 的 ...
最新文章
- 导入drupal中文语言包
- 从配置文件的角度去了解Yii2
- 【MFC】在CHtmlView中准确判断页面加载完成
- Linux学习中压缩包格式有哪些?
- 我的机器学习入门之路(上)——传统机器学习
- 目前主流的四大浏览器内核Trident、Gecko、WebKit以及Presto
- 每天一道LeetCode-----计算给定范围内所有数的与运算结果
- Redis最佳实践指南
- java自定义标签简单_JSP 自定义标签之一 简单实例
- 使用Intellij中的Spring Initializr来快速构建Spring Boot/Cloud工程
- iPad开发(相对于iPhone开发时专有的API)
- C/C++网络编程工作笔记0002---网络编程流程
- TCP/IP 协议 —— ARP
- paip.c#.net托盘图标的建立使用总结
- mysql tx read only_DB为何大量出现select @@session.tx_read_only 详解
- 动物识别系统 c语言代码_C++程序员避不开虚函数的,就像C语言程序员避不开指针一样...
- 【Ubuntu+Caffe+CPU】Cannot use GPU in CPU-only Caffe: check mode
- Detectron 创建blob的过程
- 自学Python去接单,一周就能赚取近1200块,你看不起的行业,真的很赚钱!
- SSH连接git服务器学习笔记
热门文章
- PR常见问题「六」pr去水印的几种方法
- z变换判断稳定性和因果性_LTI系统判断因果性稳定性.PPT
- 字跳三轮面试完,我想去读个硕士了T_T
- 微信也能鉴别山寨iPhone【微信高级教程2】
- mac 上 csv导入Excel 出现 “此文本文件包含的数据无法放置在一个工作表” 错误
- 稀疏数组——优化五子棋游戏的小技巧
- 知网文献免费下载方法
- linux bios 禁用usb设备,当USB在UEFI / BIOS中工作时,为什么USB在Linux中不工作?
- 主题:一个中专生:我在华为面试的真实经历,转http://www.javaeye.com/topic
- 树莓派安装FISCO-BCOS