Java IO流之PipedOutputStream和PipedInputStream分析

 

简介

    PipedOutputStream和PipedInputStream分别是管道输出流和管道输入流,是字节流的一种。两者配合使用可以进行同一个进程不同线程之间的通信,管道输入流用于向管道中读取数据,管道输出流用于向管道中写入数据。通常管道输入流和管道输出流在不同的线程中,如果在同一个线程可能存在死锁的情况。

    实现方式大致是,管道输出流与管道输出流进行关联,当一个线程向管道输出流中写入数据,实际上是将数据写到与管道输出关联的管道输入流的缓冲区中(大小为1024的字节数组),其他线程通过管道输入流读取到缓冲区中的数据,这样就是实现了线程之间的通信。而当缓冲区中的数据已经为满的情况下,管道输出流将阻塞,当缓冲区中的数据为空的情况下,管道输入流将阻塞。

                                    

PipedOutputStream介绍

    1.PipedOutputStream的构造方法

public PipedOutputStream(PipedInputStream snk)  throws IOException{ connect(snk) }
public PipedOutputStream() {}
  • 有参构造传入PipedInputStream,创建连接到指定管道输入流的管道输出流。
  • 无参构造,创建了未连接到管道输入流的管道输出流。

2.内部变量

private PipedInputStream sink;
  • 与管道输出流连接的管道输出流。

3.内部方法

public synchronized void connect(PipedInputStream snk) throws IOException {}
public void write(int b) throws IOException {}
public void write(byte b[], int off, int len) throws IOException {}
public synchronized void flush() throws IOException {}
public void close() throws IOException {}
  • connect(PipedInputStream snk)---将管道输入流和管道输出流连接。

  • write(int b)---将一个字节b写入到缓冲区.

  • write(byte b[],int off,int len)---将字节数组b的off索引开始,长度为len个字节写入到缓冲区中.

  • flush()---刷新缓冲区.

  • close()---关闭管道输出流,释放相关资源,并且流不能再写入字节.

PipedInputStream介绍

    1.PipedInputStream的构造方法

public PipedInputStream(PipedOutputStream src) throws IOException {}
//创建管道输入流,并连接到管道输出流src
public PipedInputStream(PipedOutputStream src, int pipeSize) {}
//创建管道出入流,并连接到管道输出流src,并创建指定大小pipeSize的缓冲区
public PipedInputStream() {}
//创建无连接的管道输入流
public PipedInputStream(int pipeSize) {}
//创建无连接的管道输入流,并创建指定大小PipeSize缓冲区

    2.内部变量

 protected static final int PIPE_SIZE = DEFAULT_PIPE_SIZE;
 protected byte buffer[];
 protected int in = -1;
 protected int out = 0;
  • PIPE_SIZE---默认创建的缓冲区的大小为1024字节.
  • buffer[] ---缓冲区为一字节数组.
  • in---缓冲区的索引,从管道输出流中接受到的字节存储到此位置
  • out---缓冲区的索引,管道输入流读取的下一个字节的位置.

  3.内部方法

public void connect(PipedOutputStream src) throws IOException {}
protected synchronized void receive(int b) throws IOException {}
synchronized void receive(byte b[], int off, int len) throws IOException {}
public synchronized int read(byte b[], int off, int len) throws IOException {}
public synchronized int read() throws IOException {}
public synchronized int available() throws IOException {}
public void close() throws IOException {}
  • connect(PipedOutputStream src)---将管道输入流连接到管道输出流.

  • receive(int b)---从管道输出流中接受一个字节b存储到缓冲区.

  • receive(byte b[] int off,int len)---从管道输出流中接受字节数组b中,索引off开始,长度为len的字节.

  • read(byte[] b,int off,int len)---将缓冲区的字节读取到字节数组b中索引off开始,长度为len之间的位置.最多为len个字节.

  • read()---从缓冲区读取一个字节.

  • available()---从缓冲区中可读取的字节数.

  • close()---关闭管道输入流,并释放相关资源

案例

public class PipedDemo {
  public static void main(String[] args) {
    Sender sender = new Sender();
    Receiver receiver = new Receiver();
    PipedOutputStream pops = sender.getOutputStream();
    PipedInputStream pips = receiver.getPipedInputStream();
    try {
      //管道流连接,与注释的语句相同
      pops.connect(pips);
      //pips.connect(pops);
      //开始两个线程,一个管道输入流用于读取数据,一个是管道输出流写入数据
      sender.start();
      receiver.start();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}
public class Receiver extends Thread {

  private PipedInputStream in = new PipedInputStream();

  public PipedInputStream getPipedInputStream() {
    return in;
  }

  public void run() {
    try {
      byte[] buffer = new byte[1024];
      int len = in.read(buffer);
      System.out.println(new String(buffer, 0, len));
      in.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}
public class Sender extends Thread {
  
  private PipedOutputStream out = new PipedOutputStream();
  
  public PipedOutputStream getOutputStream() {
    return out;
  }
  
  public void run() {
    try {
      String message = "The Demo is about PipedOutputStream and PipedInputStream";
      out.write(message.getBytes());
      out.close();
    }catch(IOException e) {
      e.printStackTrace();
    }
  }
}

运行结果:

The Demo is about PipedOutputStream and PipedInputStream

PipedOutputStream源码分析

package pipedDemo;

import java.io.IOException;
import java.io.PipedInputStream;

public class PipedOutputStream {

  private PipedInputStream sink;

  // 创建与管道输入流连接的管道输出流
  public PipedOutputStream(PipedInputStream snk) throws IOException {
    connect(snk);
  }

  // 创建未连接的管道输出流
  public PipedOutputStream() {}

  // 管道输出流和管道输出流连接
  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;
    // in是缓冲区buffer存储下一个字节的位置,初始化-1,表示缓冲区是空的
    snk.in = -1;
    // out是从缓冲区读取下一个字节的位置,初始化为0
    // 当in==out表示缓冲区已满
    snk.out = 0;
    snk.connected = true;
  }

  // 将字节b写入到缓冲区里面,实际存储到与管道输出流连接的管道输入流的缓冲区中.
  public void write(int b) throws IOException {
    if (sink == null) {
      throw new IOException("Pipe not connected");
    }
    sink.receive(b);
  }

  // 将字节数组b,off开始,长度为len的字节写入到缓冲区里面
  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);
  }

  // 刷新管道输出流
  public synchronized void flush() throws IOException {
    if (sink != null) {
      synchronized (sink) {
        sink.notifyAll();
      }
    }
  }

  // 关闭管道输出流,并关闭相关资源
  public void close() throws IOException {
    if (sink != null) {
      sink.receivedLast();
    }
  }
}

总结:

    1.将字节数组写入到管道输出流,其实是写入到与管道输出流连接的管道输入流中,读取数据只不过是切换到另外一个线程来读取.

    2.管道输入流里面的缓冲区实际上字节数组buffer,in表示的是缓冲区存储下一个字节的位置,in=-1时,缓冲区为空;out表示的从缓冲区里面读取下一个字节的位置.当in==out表示的是缓冲区已满了.

PipedInputStream源码分析

package pipedDemo;

import java.io.IOException;
import java.io.PipedOutputStream;

public class PipedInputputStream {
  // 管道输出流是否关闭
  boolean closedByWriter = false;
  // 管道输入流是否关闭
  volatile boolean closedByReader = false;
  // 管道输入流和管道输出流是否连接
  boolean connected = false;

  // 向缓冲区写入数据的线程
  Thread readSide;
  // 读取缓冲区数据的线程
  Thread writeSide;

  // 缓冲区默认大小为1024字节
  private static final int DEFAULT_PIPE_SIZE = 1024;
  protected static final int PIPE_SIZE = DEFAULT_PIPE_SIZE;
  // 缓冲区实际上为字节数组
  protected byte buffer[];
  // 接受管道输出流的字节存储到缓冲数组buffer的位置,in=-1时,缓冲区是空的
  protected int in = -1;
  // 管道输入流读取下一个字节的位置
  protected int out = 0;

  // 创建和管道输出流连接的管道输入流,缓冲区大小为默认的1024字节
  public PipedInputStream(PipedOutputStream src) throws IOException {
    this(src, DEFAULT_PIPE_SIZE);
  }

  // 创建和管道输入流连接的管道输出流,并指定缓冲区的大小pipeSize
  public PipedInputStream(PipedOutputStream src, int pipeSize)
      throws IOException {
    initPipe(pipeSize);
    connect(src);
  }

  // 创建未连接的管道输入流,缓冲区的大小为1024字节
  public PipedInputStream() {
    initPipe(DEFAULT_PIPE_SIZE);
  }

  // 创建未连接的管道输入流,并指定缓冲区的大小为pipeSize
  public PipedInputStream(int pipeSize) {
    initPipe(pipeSize);
  }

  // 创建指定大小pipeSize字节数组
  private void initPipe(int pipeSize) {
    if (pipeSize <= 0) {
      throw new IllegalArgumentException("Pipe Size <= 0");
    }
    buffer = new byte[pipeSize];
  }

  // 管道输出流和管道输入流连接
  public void connect(PipedOutputStream src) throws IOException {
    src.connect(this);
  }

  // 从管道输出流接受一个字节b存储到缓冲区
  protected synchronized void receive(int b) throws IOException {
    // 检查能否写入缓冲区的状态
    checkStateForReceive();
    writeSide = Thread.currentThread();
    // 缓冲区数据已满,等待管道输入流读取数据
    if (in == out)
      awaitSpace();
    // 缓冲区数据为空,重置in和out为0
    if (in < 0) {
      in = 0;
      out = 0;
    }
    // 将字节数组b存储到缓冲区buffer中位置in上,并将in自加
    buffer[in++] = (byte) (b & 0xFF);
    if (in >= buffer.length) {
      in = 0;
    }
  }

  /**
   * 接受管道输出流过来的字节数组b,off开始,长度len个字节写入缓冲区,实际写入的数据长度不一定
 是len,要看缓冲区里面可存储的位置.
   */
  synchronized void receive(byte b[], int off, int len) throws IOException {
    checkStateForReceive();
    writeSide = Thread.currentThread();
    int bytesToTransfer = len;
    while (bytesToTransfer > 0) {
// int=out,表示缓冲区已经满了,等待读取,int是表示缓冲区里面下一个字节存储的位置,out表示下一个读取字节的位置
      if (in == out)
        awaitSpace();
// nextTransferAmount表示实际缓冲区Buffer还能存储字节的长度,bytesToTransfer表示的是数组b实际上可写到缓冲区里面的长度
      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 {
// 当in=-1表示剩余还能写入buffer.length-in,当in不等于-1的时候,表示---in<out--两边虚线位置的已经存在数据
//所以剩余能写入的长度是out-in
// 缓冲区里面只要out读取过了,那么就可以进行写入
          nextTransferAmount = out - in;
        }
      }
      if (nextTransferAmount > bytesToTransfer)
        nextTransferAmount = bytesToTransfer;

      // 断言,当nextTransferAmmount大于0.程序中断
      assert (nextTransferAmount > 0);
      // 写完以后会将字节数组里面起始位置off位置移动上次已经实际写入的长度,in也会相应移动本次实际写入缓冲里面长度
      System.arraycopy(b, off, buffer, in, nextTransferAmount);
      bytesToTransfer -= nextTransferAmount;
      off += nextTransferAmount;
      in += nextTransferAmount;
      // 当in的位置如果超过缓冲区最大长度,那么就将in置为0
      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 {
    while (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;
    // 当in小于0,缓冲区为空,唤醒写入线程写入数据到缓冲区
    while (in < 0) {
      if (closedByWriter) {
        return -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();
      }
    }
    // 读取位置out的字节,out自加
    int ret = buffer[out++] & 0xFF;
    if (out >= buffer.length) {
      out = 0;
    }
    // 缓冲区数据已满,从头开始
    if (in == out) {
      /* now empty */
      in = -1;
    }

    return ret;
  }

  // 将缓冲区里面的数组读取到字节数组b的索引off开始,len个长度,所以最多读取长度是len
  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;
    }
    b[off] = (byte) c;
    int rlen = 1;
    while ((in >= 0) && (len > 1)) {

      int available;
      // 当in大于out时,即out---in之间数组可以被读取
      if (in > out) {
        available = Math.min((buffer.length - out), (in - out));
      } else {
        // 当in小于等于out,即in out----buffer.length之间的数据可被读取
        available = buffer.length - out;
      }

      // 缓冲区里面可读取的数据大于要读取的长度len-1,那么实际可以读取len-1
      // len-1原因是off+rlen
      if (available > (len - 1)) {
        available = len - 1;
      }
      // 将要读取的字节复制到字节数组b中
      System.arraycopy(buffer, out, b, off + rlen, available);
      out += available;
      rlen += available;
      len -= available;

      if (out >= buffer.length) {
        out = 0;
      }
      // 缓冲区数据已满的情况下,将in置为-1
      if (in == out) {
        in = -1;
      }
    }
    return rlen;
  }

  // 实际可以读取缓冲区里面数据的长度
  public synchronized int available() throws IOException {
    if (in < 0)
      return 0;
    // in==out表示缓冲区数据已满,可读数据长度为buffer.length;
    else if (in == out)
      return buffer.length;
    // out----in,表示out到in之间数据还未读取
    else if (in > out)
      return in - out;
    else
      // ---in <out---,当in小于out的时候,说明in到out之间数据已经读取了,
      // 剩下in+(buffer.length-out)两段之间的数据
      return in + buffer.length - out;
  }

  // 关闭管道输入流
  public void close() throws IOException {
    closedByReader = true;
    synchronized (this) {
      in = -1;
    }
  }
}

总结:

  • 管道输入流和管道输出流不要重复连接两次,否则会出现报错的情况.
  • 不要在同一个线程里面用PipedOutputStream和PipedInputStream,否则会造成死锁的情况.

分析源码可以结合一下几个图例进行分析

1.管道输出流先向缓冲区里面写入800个字节,读取线程读取100个字节.此时out<in,剩余可读取的字节数为in-out.

2.然后又向缓冲区里面写入424个字节.此时out<in,可读取的字节数为buffer.length-out/in-out.

3.再次循环写入100个字节,此时in==out表示,缓冲区数据已经满了,等待读取线程读取数据.

4.然后读取线程读取了700个字节,那么此时out>in,此时可读取的字节数为in+(buffer.length-out).

 

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值