管道流的种类
管道流是用来在多个线程之间进行信息传递的Java流。管道流分为字节流管道流和字符管道流
字节管道流:PipedOutputStream和PipedInputStream。
字符管道流:PipedWriter和PipedReader。
字节管道流&字符管道流
字节管道流是通过底层一个是byte数组存储数据的。Java的管道输入与输出实际上使用的是一个循环缓冲数组来实现的。输入流PipedInputStream从这个循环缓冲数组中读数据,输出流PipedOutputStream往这个循环缓冲数组中写入数据。当这个循环缓冲数组已满的时候,输入流PipedOutputStream所在的线程将阻塞,当这个循环缓冲数据为空的时候,输出流PipedInputStream所在的线程将阻塞。
字符管道流是通过底层一个是char数组存储数据的。
注意事项
- 管道流仅用于多个线程之间传递信息,若用在同一个线程中可能造成死锁;
- 管道流的输入和输出的成对的,一个输入流只能对应输出流,使用构造函数或者connect函数进行连接;
- 一对管道流包含一个缓冲区,其默认值为1024个字节,若要改变缓冲区大小,可以使用带有参数的构造函数;
- 管道的读写操作是互相阻塞的,当缓冲区为空时,读操作阻塞;当缓冲区满时,写操作阻塞;
- 管道依附于线程,因此若线程结束,则虽然管道流对象还在,仍然会报错“read dead end”;
- 管道流的读取方法与普通流不同,只有输出流正确close时,输出流才能读到-1值。
管道流的使用
自符管道流
import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
public class PipedDemo {
private static class Writer implements Runnable{
private PipedWriter pipedWriter;
private Writer(PipedWriter writer){
pipedWriter = writer;
}
@Override
public void run() {
int receive;
try {
while ((receive = System.in.read()) != -1){
System.out.println(Thread.currentThread().getName() + "写入字符:" + (char)receive);
pipedWriter.write(receive);
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
pipedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private static class Printer implements Runnable{
private PipedReader pipedReader;
private Printer(PipedReader reader){
this.pipedReader = reader;
}
@Override
public void run() {
int receive;
try {
while ((receive = pipedReader.read()) != -1){
System.out.println(Thread.currentThread().getName() + "打印字符:" + (char)receive);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
PipedWriter out = new PipedWriter();
PipedReader in = new PipedReader();
out.connect(in);
Thread printThread = new Thread(new Printer(in),"打印线程");
printThread.start();
Thread writeThread = new Thread(new Writer(out),"写入线程");
writeThread.start();
}
}
字节流管道
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Scanner;
public class PipedDemo01 {
private static class Writer implements Runnable{
private PipedOutputStream pipedOutputStream;
public PipedOutputStream getPipedOutputStream() {
return pipedOutputStream;
}
private Writer(PipedOutputStream outputStream){
this.pipedOutputStream = outputStream;
}
@Override
public void run() {
Scanner scanner = new Scanner(System.in);
String receive = scanner.nextLine();
System.out.println(Thread.currentThread().getName() + "写入字符串:" + receive);
try {
pipedOutputStream.write(receive.getBytes());
pipedOutputStream.flush();
}catch (Exception e){
e.printStackTrace();
}finally {
try {
pipedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private static class Printer implements Runnable{
private PipedInputStream pipedInputStream;
private Printer(PipedInputStream inputStream){
this.pipedInputStream = inputStream;
}
public PipedInputStream getPipedInputStream() {
return pipedInputStream;
}
@Override
public void run() {
try {
byte[] buf = new byte[1024];
int len = pipedInputStream.read(buf);
String receive = new String(buf,0,len);
System.out.println(Thread.currentThread().getName() + "打印字符串:" + receive);
}catch (Exception e){
e.printStackTrace();
}finally {
try {
pipedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws IOException {
PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream();
out.connect(in);
Thread writeThread = new Thread(new Writer(out),"写入线程");
writeThread.start();
Thread printThread = new Thread(new Printer(in),"打印线程");
printThread.start();
}
}
管道流源码分析
PipedReader构造器
public PipedReader(PipedWriter src) throws IOException {
this(src, DEFAULT_PIPE_SIZE);
}
public PipedReader(PipedWriter src, int pipeSize) throws IOException {
initPipe(pipeSize);
connect(src);
}
public PipedReader() {
initPipe(DEFAULT_PIPE_SIZE);
}
public PipedReader(int pipeSize) {
initPipe(pipeSize);
}
可以看到,无论哪种构造方法都调用了initPiped方法。
private void initPipe(int pipeSize) {
if (pipeSize <= 0) {
throw new IllegalArgumentException("Pipe size <= 0");
}
buffer = new char[pipeSize];
}
initPiped方法首先校验传入的参数,然后创建了传入的参数大小的char数组。
PipedWriter构造器
public PipedWriter() {
}
PipedWrier.connect()方法
public synchronized void connect(PipedReader snk) throws IOException {
if (snk == null) {
throw new NullPointerException();
} else if (sink != null || snk.connected) {
throw new IOException("Already connected");
} else if (snk.closedByReader || closed) {
throw new IOException("Pipe closed");
}
sink = snk;
snk.in = -1;//写入位置
snk.out = 0;//输出位置
snk.connected = true;
}
PipedWriter类中有个PipedReader的引用。
private PipedReader sink;
connect方法是一个同步的方法:
- 首先校验PipedReader的合法性,非空,未连接且未关闭;
- 初始化缓存数组的写入位置和输出位置。此时没有任何数据发生写入和输出,所有都是默认值。
PipedWriter.wtite()方法
public void write(int c) throws IOException {
if (sink == null) {
throw new IOException("Pipe not connected");
}
sink.receive(c);
}
写入字符到管道字符输出流中。PipedWiter.wtite()方法。其调用了PipedReader.receive()方法写入数据。
PipedReader.receive()方法
synchronized void receive(int c) 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");
}
writeSide = Thread.currentThread();
while (in == out) {
if ((readSide != null) && !readSide.isAlive()) {
throw new IOException("Pipe broken");
}
/* full: kick any waiting readers */
notifyAll();
try {
wait(1000);
} catch (InterruptedException ex) {
throw new java.io.InterruptedIOException();
}
}
if (in < 0) {
in = 0;
out = 0;
}
buffer[in++] = (char) c;
if (in >= buffer.length) {
in = 0;
}
}
首先校验管道是否正常,然后判断in == out,是则说明缓冲区已满,不能再次写入了,所以在while循环中唤醒所有读的线程(读的线程也许出于wait状态)。然后当前写入的线程休眠1s。
当否时,说明有空间可以写入数据。如果in小于0,说明是首次进入,初始化,in= 0,out = 0,。如果写入的数据超过缓存数组的长度,那就重置为0,从头开始写入。此时就把缓存数组当做是一个环形。
PipedReader.read()方法
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++];
if (out >= buffer.length) {
out = 0;
}
if (in == out) {
/* now empty */
in = -1;
}
return ret;
}
首先是管道状态检验。while循环监听判断是否有写线程写数据,如果没有则等待(每秒检查一次),并唤醒写线程(写线程可能wait)。读取buffer中的数据。out>=0读到buffer的最后一个元素,则把out置为0,下次从下标0开始继续读。如果在read方法读取数据时,发生in == out,则把in置为-1。置为初始状态。相当于清空了缓存区,从缓冲区下标0开始读写。
管道流的原理其实很简单,就是通过wait()和notifyAll()来控制buffer是否可读写。