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

简介

PipedOutputStream和PipedInputStream主要用于线程之间的通信 。二者必须配合使用,也就是一段写入,另一端接收。本质上也是一个中间缓存区,讲数据缓存在PipedInputStream的数组当中,等待PipedOutputStream的读取。
PipedInputStream的缓冲区中循环缓冲的思想很有意思。

PS:虽然这个也叫管道,但是这和进程之间的管道通信没有任何关系。这里的管道流是基于Java用户层的代码实现的,而经常通信是基于内核态的程序的通信。

源码分析

PipedOutputStream

public
class PipedOutputStream extends OutputStream {
	// 需要传入的输入流
    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;
        // 初始化输入流的读写位置
        snk.in = -1;
        // 初始化输出流的读写位置
        snk.out = 0;
        // 将输入流连接标志置位
        snk.connected = true;
    }

	// 将一个int类型数据写入到输出流,这里就会将它传给输入流
    public void write(int b)  throws IOException {
        if (sink == null) {
            throw new IOException("Pipe not connected");
        }
        sink.receive(b);
    }

	// 写入字节数组的指定位置
    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 {
	// 输出流是否被关闭
    boolean closedByWriter = false;
    // 输入流是否被关闭,这里修饰了volatile
    volatile boolean closedByReader = false;
    // 输入输出的连接标记
    boolean connected = false;
	// 需要传入的读写线程
    Thread readSide;
    Thread writeSide;

	// 管道默认可以缓存的大小
    private static final int DEFAULT_PIPE_SIZE = 1024;

    protected static final int PIPE_SIZE = DEFAULT_PIPE_SIZE;

	// 缓冲区
    protected byte buffer[];

	// 当前缓冲区中应该写入的位置
    protected int in = -1;

	// 当前缓冲区可以读取的位置
    protected int out = 0;

	// 传入输出流的构造
    public PipedInputStream(PipedOutputStream src) throws IOException {
        this(src, DEFAULT_PIPE_SIZE);
    }

	// 传入输出流和管道缓存大小的构造
    public PipedInputStream(PipedOutputStream src, int pipeSize)
            throws IOException {
         initPipe(pipeSize);
         connect(src);
    }

	// 默认构造
    public PipedInputStream() {
        initPipe(DEFAULT_PIPE_SIZE);
    }

	// 传入管道大小的构造
    public PipedInputStream(int pipeSize) {
        initPipe(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);
    }

	// 接收一个字节,同步的
    protected synchronized void receive(int b) throws IOException {
    	// 检测管道的状态
        checkStateForReceive();
        // 读取当前写入线程
        writeSide = Thread.currentThread();
        // 写入指针等于读取指针,说明缓冲区满了,通知其他读线程尽快来读
        // 当前线程会进入等待状态
        if (in == out)
            awaitSpace();
        // 输出流的写入位置小于0
        if (in < 0) {
            in = 0;
            out = 0;
        }
        // 写入字节,只取低八位
        buffer[in++] = (byte)(b & 0xFF);
        // 循环缓冲指针复位
        if (in >= buffer.length) {
            in = 0;
        }
    }

	// 写入一堆
    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;
            }
        }
    }

	// 判断连接状态
    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();
            }
        }
    }

	// 当输出流被关闭的时候使用
    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) {
                /* closed by writer, return EOF */
                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();
            }
        }
        // 获取当前字节
        int ret = buffer[out++] & 0xFF;
        // 循环缓冲复位
        if (out >= buffer.length) {
            out = 0;
        }
        // 表示读完了,重置写指针
        if (in == out) {
            /* now empty */
            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;
        }
        // 放入第一个字节
        b[off] = (byte) c;
        int rlen = 1;
        // 循环读取剩下的字节
        while ((in >= 0) && (len > 1)) {

            int available;

			// 其实这里就是一个循环缓冲的剩余缓冲长度计算了,当写指针超出了缓冲区的长度,就会回到-1,计算长度的方式就不同了
            if (in > out) {
            	// 写指针没超,那么长度就应该就是写指针位置减去读指针,这里取小是反之数组越界
                available = Math.min((buffer.length - out), (in - out));
            } else {
            	// 写指针超了,回到了-1,那么剩余长度就是数组长度减去读指针
                available = buffer.length - out;
            }

            // 防止数组与越界
            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)
        	// 当in == -1说明刚被读完或者刚初始化,缓冲区没有数据
            return 0;
        else if(in == out)
        	// 只有缓冲区被写满的时候,二者才会相等,说明缓冲区的数据满了,如果是被读完,in会被置-1
            return buffer.length;
        else if (in > out)
        	// 还有数据
            return in - out;
        else
        	// 循环缓冲,in在out后面,说明in已经跑完一圈了
            return in + buffer.length - out;
    }

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

总结

PipedOutputStream特点

  • 本质就是调用PipedInputStream的接口,将数据写进PipedInputStream的缓冲区当中。
  • 一个输出只能一个输入连接。
  • 和之前的ByteArrayInputStream 一样,操作的数据都是字节类型。

PipedInputStream特点

  • 内部主要由缓冲数组、读指针和写指针构成。
  • 由于这两个流是用于线程之间通信,所以他们是需要保证线程安全的,他们对外的函数都是有同步锁修饰的,同时只能有一个线程进行读取获取写入,其实效率不高。
  • 当生产者写入的时候发现缓冲区满了,就会进入等待状态,等待消费者消费数据,再将他们唤醒。
  • 当消费者读取数据的时候发现缓冲区是空的,那么就会进入等待,等待生产者写入数据,再将他们唤醒。

缓冲区特点

  • 缓冲区其实采用的是一个循环缓冲的形式,在读取数据的时候,读取的是读指针当前的位置,读一个增加一个,但是当读指针和写指针相同的时候,in就会被置为-1,这是为了后面缓冲区数据满的时候,读指和写指针的相同的情况进行区分,也就是说,读指针和写指针相等的时候,就是数据满的时候;当读指针超出了缓冲区数组边界,那么就会被置为0,这样往复就是循环缓冲的思想。
  • 当数据写入的时候,数据就会写入写指针的位置,当写指针超出了数组边界,就会被置为0;当写指针等于读指针,说明写指针已经超圈了,那么缓存区的可用长度就是整个缓冲区的大小,不能再超过读指针,不然会被理解为可用长度是大于的那一部分。

缓冲区有效数据长度的情况如下图所示:

在这里插入图片描述

原文地址:https://www.cnblogs.com/lippon/p/14119151.html