[重学Java基础][Java IO流][Part.10] 管道字节输入输出流

[重学Java基础][Java IO流][Part.10] 字节管道输入输出流

总述

PipedOutputStream和PipedInputStream分别是管道字节输出流和管道字节输入流。
多线程可以通过管道进行线程间的通讯,在使用管道通信时,必须将PipedOutputStream和PipedInputStream配套使用。
使用管道通信时,基于生产者-消费者模式,一般流程是:PipedOutputStream对象表示流发送端,PipedInputStream对象则表示流接收端,使用两个对象的connect()方法连接两端。我们在线程A中向PipedOutputStream中写入数据,这些数据会自动的发送到与PipedOutputStream对应的PipedInputStream中,进而存储在PipedInputStream的缓冲中;此时,线程B通过读取PipedInputStream中的数据。就可以实现,线程A和线程B的通信。

PipedInputStream

概述

源码分析

成员属性

管道输出流是否关闭
boolean closedByWriter;
管道输入流是否关闭
volatile boolean closedByReader;
管道输入流与管道输出流是否连接
boolean connected;
读取管道中数据的线程
Thread readSide;
写入管道中数据的线程
Thread writeSide;
管道默认缓冲区大小 为1024字节
private static final int DEFAULT_PIPE_SIZE = 1024;
管道大小1024字节
protected static final int PIPE_SIZE = 1024;
管道缓冲字节数组
protected byte[] buffer;
下一个输入字节位置
protected int in;
下一个输出字节位置
protected int out;

成员方法

构造函数

传入一个PipedOutputStream对象 并调用第二个构造函数重载 初始化缓冲区并连接管道
public PipedInputStream(PipedOutputStream src) throws IOException {
    this(src, 1024);
}

设置输入输出位置 初始化缓冲区并连接管道
public PipedInputStream(PipedOutputStream src, int pipeSize) throws IOException {
    this.in = -1;
    this.out = 0;
    this.initPipe(pipeSize);
    this.connect(src);
}

无参构造  设置输入输出位置并初始化缓冲区
public PipedInputStream() {
    this.in = -1;
    this.out = 0;
    this.initPipe(1024);
}
按指定大小初始化缓冲区
public PipedInputStream(int pipeSize) {
    this.in = -1;
    this.out = 0;
    this.initPipe(pipeSize);
}

初始化缓冲区 连接方法

  按指定大小初始化缓冲区
  private void initPipe(int pipeSize) {
    if (pipeSize <= 0) {
        throw new IllegalArgumentException("Pipe Size <= 0");
    } else {
        this.buffer = new byte[pipeSize];
    }
}

管道连接 实际调用了PipedOutputStream的连接方法 
public void connect(PipedOutputStream src) throws IOException {
    src.connect(this);
}

接收流内容方法 会在管道字节输出流PipedOutputStream的write()方法中被调用

protected synchronized void receive(int b) throws IOException {
        检查管道状态
        this.checkStateForReceive();
        获取当前输入的线程
        this.writeSide = Thread.currentThread();
        如果读出位置=写入位置 说明已经读完 线程等待
        if (this.in == this.out) {
            this.awaitSpace();
        }

    if (this.in < 0) {
        this.in = 0;
        this.out = 0;
    }
    读入数据保存到缓冲区 
    this.buffer[this.in++] = (byte)(b & 255);
    if (this.in >= this.buffer.length) {
        this.in = 0;
    }

}
读入字节数组 并指定始终位置
synchronized void receive(byte[] b, int off, int len) throws IOException {
    检查管道状态
    this.checkStateForReceive();
    this.writeSide = Thread.currentThread();
    要转换的字节长度
    int bytesToTransfer = len;

    while(bytesToTransfer > 0) {
        读出游标位置=写入游标位置 说明已经读完 线程等待
        if (this.in == this.out) {
            this.awaitSpace();
        }

        int nextTransferAmount = 0;
        如果读出小于写入 则设置下面要被转换的字节大小
        nextTransferAmount=buffer.length - in;
        if (this.out < this.in) {
            nextTransferAmount = this.buffer.length - this.in;
        } else if (this.in < this.out) {
            如果读出大于写入的情况
            if (this.in == -1) {
                输入=-1 说明已经读完
                则设置读入=写出=0
                this.in = this.out = 0;
                nextTransferAmount = this.buffer.length - this.in;
            } else {
                nextTransferAmount = this.out - this.in;
            }
        }
        若下面要被转换的字节nextTransferAmount大于要转换的字节长度bytesToTransfer
        if (nextTransferAmount > bytesToTransfer) {
        则设置nextTransferAmount=bytesToTransfer的值
            nextTransferAmount = bytesToTransfer;
        }

        使用断言,若nextTransferAmount <= 0,则退出此进程
        assert nextTransferAmount > 0;

        数据写入到缓冲数组
        System.arraycopy(b, off, this.buffer, this.in, nextTransferAmount);
        bytesToTransfer -= nextTransferAmount;
        off += nextTransferAmount;
        this.in += nextTransferAmount;
        if (this.in >= this.buffer.length) {
            this.in = 0;
        }
    }

}

检查管道状态

private void checkStateForReceive() throws IOException {
检查管道状态 
    if (!this.connected) {
    未连接抛出未连接异常
        throw new IOException("Pipe not connected");
    } else if (!this.closedByWriter && !this.closedByReader) {
        if (this.readSide != null && !this.readSide.isAlive()) {
        如果读入线程为空或者已终止 则抛出 读入终止异常
            throw new IOException("Read end dead");
        }
    } else {
    否则抛出管道关闭异常
        throw new IOException("Pipe closed");
    }
}

等待方法

前面提到 当写入管道的数据被读出完毕后 调用此方法 让线程处于等待状态

private void awaitSpace() throws IOException {
如果管道数据已读完 则每隔1秒唤醒线程 看是否有数据继续写入
    while(this.in == this.out) {
        this.checkStateForReceive();
        this.notifyAll();

        try {
            this.wait(1000L);
        } catch (InterruptedException var2) {
            throw new InterruptedIOException();
        }
    }

}
输入端关闭后 调用读出端接收完毕方法
synchronized void receivedLast() {
    this.closedByWriter = true;
    this.notifyAll();
}

读入数据并存储到byte数组中方法

不是此类的主流功能 且和其他类类似功能大同小异 
 public synchronized int read(byte b[], int off, int len)  throws IOException 
 {
 ……
 }

从此字节管道输入流对象中读取的字节数

    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;
        }
    }

PipedOutputStream

源码分析

成员属性

  字节管道输入流对象
  private PipedInputStream sink;

成员方法

构造方法

传入一个PipedInputStream参数并连接两个管道
public PipedOutputStream(PipedInputStream snk) throws IOException {
        this.connect(snk);
    }
空构造
public PipedOutputStream() {
}

管道连接

public synchronized void connect(PipedInputStream snk) throws IOException {
    if (snk == null) {
        throw new NullPointerException();
    } else if (this.sink == null && !snk.connected) {
        设置初始化参数
        this.sink = snk;
        snk.in = -1;
        snk.out = 0;
        snk.connected = true;
    } else {
        throw new IOException("Already connected");
    }
}

写入到管道

public void write(int b) throws IOException {
    if (this.sink == null) {
        throw new IOException("Pipe not connected");
    } else {
    内部调用了PipedInputStream的接收方法
        this.sink.receive(b);
    }
}

写入字节数组到管道 并指定始终位置

public void write(byte[] b, int off, int len) throws IOException {
    if (this.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) {
        if (len != 0) {
        内部调用了PipedInputStream的接收方法
            this.sink.receive(b, off, len);
        }
    } else {
        throw new IndexOutOfBoundsException();
    }
}

刷新管道方法 实质上是调用管道输入流的notifyAll()方法
目的是让“管道输入流”放弃对当前资源的占有,
让其它的等待线程(等待读取管道输出流的线程)读取“管道输出流”的值。

public synchronized void flush() throws IOException {
    if (this.sink != null) {
        PipedInputStream var1 = this.sink;
        synchronized(this.sink) {
            this.sink.notifyAll();
        }
    }

}

关闭方法

public void close() throws IOException {
    if (this.sink != null) {
        this.sink.receivedLast();
    }

}

代码示例

public static void main(String[] args) throws Exception {
    PipedInputStream pis = new PipedInputStream();
    PipedOutputStream pos = new PipedOutputStream();
    pos.connect(pis);

    Runnable sender = () -> sendeMessage(pos);
    Runnable receiver = () -> receiveMessage(pis);
    new Thread(sender).start();
    new Thread(receiver).start();
}

public static void sendeMessage(PipedOutputStream pos) {
    try {
        for (int i = 1; i <= 50; i++) {
            pos.write((byte) i);
            pos.flush();
            System.out.println("发送: " + i);
            Thread.sleep(500);
        }
        pos.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
public static void receiveMessage(PipedInputStream pis) {
    try {
        int num = -1;
        while ((num = pis.read()) != -1) {
            System.out.println("接收: " + num);
        }
        pis.close();
    } catch (Exception e) {
        e.printStackTrace();
    }

}

输出结果

发送: 1
接收: 1
发送: 2
接收: 2
发送: 3
接收: 3
发送: 4
接收: 4
发送: 5
接收: 5
发送: 6
接收: 6
发送: 7
接收: 7
发送: 8
接收: 8
发送: 9
接收: 9
发送: 10
接收: 10
发送: 11
接收: 11
……
发送: 50
接收: 50

猜你喜欢

转载自blog.csdn.net/u011863951/article/details/80079141