本篇主要从分析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();                                                      //永远是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.2 PipedOutputStream

// 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();         }

}

}

2 Buffer的状态

上图是PipedInputStream中缓存的状态图。在程序中我们利用了byte数组,循环地向其中写入数据,写入有一个cursor(in),读出也有一个cursor(out)。上图表示in和out不同位置时,buffer中的各个位置的状态。蓝色的代表可以读取的字节。白色的表示此位置没有字节,或者此位置已经被PipedInputStream读取了。

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.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的简洁阐述!

以上的例子其实是一个典型的使用适配器。

6 JMS的架构

JMS为J2EE部分的面向消息中间件的API。JMS的Queue、Topic某种意义上就是我们上面Chanel移到网络的其它一段――服务器上的一个例子。同时该Chanel得到了很多强化。如:1.支持交易;2.支持持久化……

在J2EE中JMS是一个比较重要的方向,大型的企业应用中都会使用。不过J2EE中给出了其API,背后的理念还是相当丰富的!(具体细节以后会有相关文章!!唉,还是因为忙!!)

从PipedInputStream/PipedOutputStream谈起的更多相关文章

  1. java 多线程:线程通信-等待通知机制wait和notify方法;(同步代码块synchronized和while循环相互嵌套的差异);管道通信:PipedInputStream;PipedOutputStream;PipedWriter; PipedReader

    1.等待通知机制: 等待通知机制的原理和厨师与服务员的关系很相似: 1,厨师做完一道菜的时间不确定,所以厨师将菜品放到"菜品传递台"上的时间不确定 2,服务员什么时候可以取到菜,必 ...

  2. PipedInputStream/PipedOutputStream原理

    PipedInputStream类与PipedOutputStream类用于在应用程序中创建管道通信.一个PipedInputStream实例对象必须和一个PipedOutputStream实例对象进 ...

  3. 系统学习 Java IO (六)----管道流 PipedInputStream/PipedOutputStream

    目录:系统学习 Java IO---- 目录,概览 PipedInputStream 类使得可以作为字节流读取管道的内容. 管道是同一 JVM 内的线程之间的通信通道. 使用两个已连接的管道流时,要为 ...

  4. java下管道流 PipedOutputStream 与PipedInputStream

    package cn.stat.p2.demo; import java.io.IOException; import java.io.PipedInputStream; import java.io ...

  5. Java I/O流-PipedInputStream、PipedOutputStream

    一.整体代码图 PipedStreamDemo.java import java.io.*; class PipedStreamDemo { public static void main(Strin ...

  6. PipedInputStream和PipedOutputStream详解

    PipedInputStream类与PipedOutputStream类用于在应用程序中创建管道通信.一个PipedInputStream实例对象必须和一个PipedOutputStream实例对象进 ...

  7. Java-IO之管道(PipedInputStream和PipedOutputStream)

    java中PipedInputStream和PipedOutputStream分别是管道输入流和管道输出流,它的作用是让多线程可以通过管道进行线程间的通讯,在使用管道通信时,必须将PipedInput ...

  8. java io系列04之 管道(PipedOutputStream和PipedInputStream)的简介,源码分析和示例

    本章,我们对java 管道进行学习. 转载请注明出处:http://www.cnblogs.com/skywang12345/p/io_04.html java 管道介绍 在java中,PipedOu ...

  9. Java IO源码分析(三)——PipedOutputStream和PipedInputStream

    简介 PipedOutputStream和PipedInputStream主要用于线程之间的通信 .二者必须配合使用,也就是一段写入,另一端接收.本质上也是一个中间缓存区,讲数据缓存在PipedInp ...

  10. 管道流_PipedInputStream与PipedOutputStream

    输入和输出可以直接进行连接,通过结合线程使用(一个线程用于写,另一个用于读,因为管道输入流(读)是要读取管道输出流的数据的,又因为输入流中的read方法是阻塞式的,当两个流在同一个线程中时,输入流的r ...

随机推荐

  1. 【YashanDB数据库】YAS-02032 column type is incompatible with referenced column type

    [标题]错误码处理 [问题分类]外键约束创建报错 [关键字]YAS-02032 [问题描述]设置外键约束报错,数据元数据不正确. [问题原因分析]外键字段类型不支持,比如varchar2(64) ,指 ...

  2. springboot 前端访问服务器上的图片及附件

    一.需求 后端是springboot,附件上传到服务器上,前端访问服务器上的附件,如:显示图片.视频.文件等 二.解决方法 springboot 中进行资源映射,根据路径将磁盘上的文件映射为资源返回到 ...

  3. 使用gin实现简单的注册和登录功能

    一.前言 使用了gorm操作数据库,后端基于gin框架,只是一个简单的注册和登录与数据库交互的后端实现例子. 二.目录结构 -templates --regist.html --login.html ...

  4. SQL Server – 树结构 (二叉树, 红黑树, B-树, B+树)

    前言 很久以前有学习过各种树结构, 但后来真的没有在实际项目中运用到. 毕竟我主要负责的都是写业务代码. 太上层了 但是忘光光还是很可惜的. 所以久久可以复习一下. 记得概念也好, 帮助思考. 参考: ...

  5. 从0开始计算机体系结构的学习(一):FGPA预备知识与Vivado环境搭建

    引入与预备知识 什么是FPGA? FPGA(Field-Programmable Gate Array,现场可编程门阵列)是一种集成电路(IC),其硬件功能可以通过用户在现场编程来定义.与传统的ASI ...

  6. IDEA 换了电脑,如何导入和导出配置?

    前言 我们在使用IDEA开发时,经常会设置各种各样的配置,时间一长,就会有很多个性化的东西.用起来也越来越顺手.   不过这里可能会有个问题,那就是哪一天我们要换个电脑了,或者想安装新版本的IDEA时 ...

  7. win10安装linux的gcc

    mysy2下载gcc  过程比较艰苦,2024年秋冬讲课,被linux毒打了3天 pacman -S mingw-w64-ucrt-x86_64-gcc   这个一次成功,不行继续接大招 实在不行安装 ...

  8. 大模型应用开发初探 : 基于Coze创建Agent

    大家好,我是Edison. 最近学习了一门课程<AI Agent入门实战>,了解了如何在Coze平台上创建AI Agent,发现它对我们个人(C端用户)而言十分有用,分享给你一下. Coz ...

  9. Android Perfetto 系列 2:Perfetto Trace 抓取

    使用 Perfetto 分析问题跟使用 Systrace 分析问题的步骤是一样的: 首先你需要抓取 Perfetto 文件 在ui.perfetto.dev 中打开 Trace 文件进行分析或者使用命 ...

  10. dotnet定义扩展方法

    // 扩展方法 // 1.创建静态类静态方法 2. 在静态方法中参数中使用 this 关键字指定需要扩展的类 // 密封类 不能直接继承,通过扩展方法,拿到父类的属性和方法进行扩展补充