Java IO PipedInputStream与 PipedOutputStream

本文详细解析了Java中的PipedInputStream和PipedOutputStream的工作原理,包括如何通过管道流实现线程间的数据通信,以及源码级的解释。

代码示例

public class PipedInputOutputDemo {


    public static void main(String[] args) throws IOException{
        PipedOutputStream outputStream = new PipedOutputStream();
        PipedInputStream inputStream = new PipedInputStream(outputStream);

        Thread writeSide = new Thread(() -> {
            try{
                for(int i = 0; i < 10; i++){
                    Thread.sleep(1000);
                    outputStream.write(("data"+i+"\n").getBytes());

                }
                outputStream.close();

            }catch (IOException | InterruptedException e){
                e.printStackTrace();
            }


        });

        Thread readeSide = new Thread(() -> {
            try{
                int data = -1;
                while ((data = inputStream.read()) != -1){
                    System.out.print((char) data);
                }
                inputStream.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        });

        writeSide.start();
        readeSide.start();

    }
}

Java IO PipedInputStream与 PipedOutputStream 代码示例

PipedOutputStream 源码解析


/**
 * 1. 可以将一个管道输出流连接到一个管道输入流,来创建一个通信管道。管道的数据发送端为管道输出流。
 * 2. 通常,一个线程将数据写入到管道输出流(PipedOutputStream),其他线程可以通过与管道输出流相连接的管道输入流(PipedInputStream)来读取数据。
 * 3. 不推荐在一个线程中同时使用管道输入输出流,可能会导致死锁。
 * 4. 如果从相连接的管道输入流读取数据的线程已经不再存活,那么就代表这个管道已经损坏。
 */
public
class PipedOutputStream extends OutputStream {

	/**
	* 用来接收数据的管道输入流,命名为水池。就像咱们厨房的洗碗池一样,水龙头作为数据发送端的管道输出流,水池作为数据的接收端管道输入流。
	*/
    private PipedInputStream sink;

    /**
     * 创建一个管道输入流,并连接到指定的管道输入流(入参)。写入输出流的数据可以在相连的输入流直接获取。
     */
    public PipedOutputStream(PipedInputStream snk)  throws IOException {
        connect(snk);
    }

    /**
     * 创建一个没有任何连接的空的管道输出流。 但是在使用之前臂粗连接一个管道输入流。不然水龙头(管道)里的水(数据)没地方排啊!
     */
    public PipedOutputStream() {
    }

    /**
     * 给这个管道输出流连接一个接收者。
     * 如果已经被连接到其他管道输出流了,抛出IOException。
     * snk.in: 管道输入流下一个写入位,(从输出流流出来自动写入)
     * snk.out: 管道输入流下一个读取位。
     */
    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;
    }

    /**
     * 写入字节到管道输出流。
     * 如果还没有连接到输入流,则抛出IOException。
     * 相连管道输入流自动接收写入字节。
     */
    public void write(int b)  throws IOException {
        if (sink == null) {
            throw new IOException("Pipe not connected");
        }
        sink.receive(b);
    }

    /**
     * 从字节数组b的下标off开始,连续写入len个字节到管道输出流。同样的,与其相连的管道输入流会自动读取这这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();
        }
    }
}

PipedInputStream 源码解析


/**
 * 一个管道输入流必须连接到一个管道输出流。连接之后管道输入流就可以提供任何被写入管道输出流的数据。管道输入流包含一个缓存数组,用来实现读写分离。
 * 如果往管道输出流写数据的线程死掉了,那么就认为这个管道是损坏了的。
 */
public class PipedInputStream extends InputStream {

	/**
	* 标记与其相连的管道输出流是否被调用过close方法	
	*/
    boolean closedByWriter = false;
	
	/**
	* 标记本身的close方法有没有被调用
	*/
    volatile boolean closedByReader = false;

	/**
	* 标记是否连接有输出流
	*/
    boolean connected = false;

	/**
	* 读数据线程
	*/
    Thread readSide;
    
	/**
	* 写数据线程
	*/
    Thread writeSide;

	/**
	* 默认管道大小为 1024 字节
	*/
    private static final int DEFAULT_PIPE_SIZE = 1024;

    /**
     * 管道循环输入buffer的默认值为 1024 字节。
  	 */
    protected static final int PIPE_SIZE = DEFAULT_PIPE_SIZE;

    /**
     * 可循环buffer,流入数据存放的地方。
     * @since   JDK1.1
     */
    protected byte buffer[];

    /**
     * buffer的下一个写入位。写入数据来源于相连接的管道输出流。
     * 0代表buffer是空的。
     * in==out代表buffer已满。
     */
    protected int in = -1;

    /**
     * bufferd的下一个读取位。
     */
    protected int out = 0;

    /**
     * 创建一个管道输入流,默认1024字节buffer,并与给定的管道输出流相连。
     */
    public PipedInputStream(PipedOutputStream src) throws IOException {
        this(src, DEFAULT_PIPE_SIZE);
    }

    /**
     * 创建一个指定buffer大小的管道输入流,并连接到指定的管道输出流。
     */
    public PipedInputStream(PipedOutputStream src, int pipeSize)
            throws IOException {
         initPipe(pipeSize);
         connect(src);
    }

    /**
     * 创建一个默认大小的管道输入流。创建后无法直接使用,使用前必须连接到一个管道输出流。
     */
    public PipedInputStream() {
        initPipe(DEFAULT_PIPE_SIZE);
    }

    /**
     * 创建一个指定buffer大小的管道输入流。创建后无法直接使用,使用前必须连接到一个管道输出流。
     */
    public PipedInputStream(int pipeSize) {
        initPipe(pipeSize);
    }

	/**
	* 初始化管道,即创建一个名为buffer的字节数组,数组长度即为管道大小。
	*/
    private void initPipe(int pipeSize) {
         if (pipeSize <= 0) {
            throw new IllegalArgumentException("Pipe Size <= 0");
         }
         buffer = new byte[pipeSize];
    }

    /**
     * 连接到指定的管道输出流,即将改输入流对象赋值给指定输出流PipedOutputStream 的私有对象 PipedInputStream sink。
     * 管道输入输出流必须一一对应。否则抛出IOException。
     */
    public void connect(PipedOutputStream src) throws IOException {
        src.connect(this);
    }

    /**
     * 接收一个字节数据,存放到可循环buffer中。
     */
    protected synchronized void receive(int b) throws IOException {
        checkStateForReceive();
        writeSide = Thread.currentThread();
        if (in == out)
            awaitSpace();
        if (in < 0) {
            in = 0;
            out = 0;
        }
        buffer[in++] = (byte)(b & 0xFF);
        if (in >= buffer.length) {
            in = 0;
        }
    }

    /**
     * 从给定数组b的off下标开始,接收len个字节的数据到buffer。
     */
    synchronized void receive(byte b[], int off, int len)  throws IOException {
        checkStateForReceive();
        writeSide = Thread.currentThread();
        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;
            }
        }
    }

	/**
	* 检查管道状态:
	* 1. 如果没有连接管道输出流(数据源),抛出异常 "Pipe not connected"。
	* 2. 如果输入/出流任意一方关闭,抛出异常 "Pipe closed"。
	* 3. 如果读数线程不是active状态,抛出异常 "Read end dead"。
	*/
    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();

            /* full: kick any waiting readers */
            notifyAll();
            try {
                wait(1000);
            } catch (InterruptedException ex) {
                throw new java.io.InterruptedIOException();
            }
        }
    }

    /**
     * Notifies all waiting threads that the last byte of data has been
     * received.
     */
    synchronized void receivedLast() {
        closedByWriter = true;
        notifyAll();
    }

    /**
     * 读取下一个字节数据(0-255)。 这个方法是阻塞的,直到有数据可读或流已经关闭或者出现异常。
     */
    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) {
                /* closed by writer, return EOF */
                return -1;
            }
            if ((writeSide != null) && (!writeSide.isAlive()) && (--trials < 0)) {
                throw new IOException("Pipe broken");
            }
            /* might be a writer waiting */
            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) {
            /* now empty */
            in = -1;
        }

        return ret;
    }

    /**
     * 从输入流中读取len个字节到给定字节数组,从下标off开始记录。
     */
    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;
        }

        /* possibly wait on the first character */
        int c = read();
        if (c < 0) {
            return -1;
        }
        b[off] = (byte) c;
        int rlen = 1;
        while ((in >= 0) && (len > 1)) {

            int available;

            if (in > out) {
                available = Math.min((buffer.length - out), (in - out));
            } else {
                available = buffer.length - out;
            }

            // A byte is read beforehand outside the loop
            if (available > (len - 1)) {
                available = len - 1;
            }
            System.arraycopy(buffer, out, b, off + rlen, available);
            out += available;
            rlen += available;
            len -= available;

            if (out >= buffer.length) {
                out = 0;
            }
            if (in == out) {
                /* now empty */
                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;
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

i余数

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值