1.引言
Java I/O系统是建立在数据流概念之上的,而在UNIX/Linux中有一个类似的概念,就是管道,它具有将一个程序的输出当作另一个程序的输入的能力。在Java中,可以使用管道流进行线程之间的通信,输入流和输出流必须相连接,这样的通信有别于一般的Shared Data通信,其不需要一个共享的数据空间。
2.相关类及其关系
1)字节流:
分为管道输出流(PipedOutputStream)和管道输入流(PipedInputStream),利用 java.io.PipedOutputStream和java.io.PipedInputStream可以实现线程之间的二进制信息传输。如果要进行管道输出,则必须把输出流连在输入流上。 java.io.PipedOutputStream是java.io.OutputStream的直接子类,而java.io. PipedInputStream是java.io.InputStream的直接子类。PipedOutputStream和 PipedInputStream往往成对出现、配合使用。举例说明:
package io;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class IOTest7
{
public static void main(String[] args)
{
Send send = new Send();
Receive receive = new Receive();
// 输出管道流链接到输入管道流,这也就是为什么把一个线程输出当做另一个线程输入
try
{
send.getPipedOutputStream().connect(receive.getPipedInputStream());
}
catch (IOException e)
{
e.printStackTrace();
}
new Thread(send).start();
new Thread(receive).start();
}
}
// 发送数据(往哪里写)
class Send implements Runnable
{
private PipedOutputStream pipedOutputStream;
public Send()
{
this.pipedOutputStream = new PipedOutputStream();
}
@Override
public void run()
{
String str = "Hello World!!!";
try
{
pipedOutputStream.write(str.getBytes());
}
catch (IOException e)
{
e.printStackTrace();
}
}
public PipedOutputStream getPipedOutputStream()
{
return this.pipedOutputStream;
}
}
// 从哪里接受(好比一个程序的输入作为该类的输入)
class Receive implements Runnable
{
private PipedInputStream pipedInputStream;
public Receive()
{
this.pipedInputStream = new PipedInputStream();
}
@Override
public void run()
{
byte[] byteArr = new byte[1024];
int length = 0;
try
{
length = pipedInputStream.read(byteArr);
System.out.println("接收的内容为:" + new String(byteArr, 0, length));
}
catch (IOException e)
{
e.printStackTrace();
}
}
public PipedInputStream getPipedInputStream()
{
return this.pipedInputStream;
}
}
我们可以看到使用管道流,通过connect方法进行连接,实现了Send线程和Receive线程之间的通信。
注意:
PipedInputStream中实际是用了一个1024字节固定大小的循环缓冲区。写入PipedOutputStream的数据实际上保存到对应的 PipedInputStream的内部缓冲区。从PipedInputStream执行读操作时,读取的数据实际上来自这个内部缓冲区。如果对应的 PipedInputStream输入缓冲区已满,任何企图写入PipedOutputStream的线程都将被阻塞。而且这个写操作线程将一直阻塞,直至出现读取PipedInputStream的操作从缓冲区删除数据。这也就是说往PipedOutputStream写数据的线程Send若是和从PipedInputStream读数据的线程Receive是同一个线程的话,那么一旦Send线程发送数据过多(大于1024字节),它就会被阻塞,这就直接导致接受数据的线程阻塞而无法工作(因为是同一个线程嘛),那么这就是一个典型的死锁现象,这也就是为什么javadoc中关于这两个类的使用时告诉大家要在多线程环境下使用的原因了。
应用:过滤器模式
使用这个模式的典型例子是Unix的shell命令。这个模式的好处在于过滤器无需知道它与何种东西进行连接,并且这可以实现并行,而且系统的可扩展性可以根据添加删除或者改变Filter进行增强。
在这举一个不断计算平均值的例子,producer作为前端的数据源,不断产生随机数,通过pipe进入filter进行数据处理,然后通过第二个pipe就行后端处理。
package io;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Random;
public class IOTest7
{
public static void main(String[] args) throws IOException
{
//一个输出管道流到一个输入管道流,同时把输入管道流经过一定的数据处理后作为输出到另一个输入管道流
PipedOutputStream pout1 = new PipedOutputStream();
PipedInputStream pin1 = new PipedInputStream(pout1);
PipedOutputStream pout2 = new PipedOutputStream();
PipedInputStream pin2 = new PipedInputStream(pout2);
/* construct threads */
Producer prod = new Producer(pout1);
Filter filt = new Filter(pin1, pout2);
Consumer cons = new Consumer(pin2);
/* start threads */
prod.start();
filt.start();
cons.start();
}
}
// 前端:该类的作用是产生随机数,并将其放到管道1的输出流中
class Producer extends Thread
{
private DataOutputStream out;// DataOutputStream是用于写入一些基本类型数据的类,此类的实例用于生成伪随机数流
private Random rand = new Random();
public Producer(OutputStream os)
{
out = new DataOutputStream(os);
}
public void run()
{
while (true)
{
try
{
double num = rand.nextDouble();
// 将double值直接写入流
out.writeDouble(num);
System.out.println(this.getName() + "--写入流中的值是 :" + num);
out.flush();
sleep(Math.abs(rand.nextInt() % 10));// 随机休眠一段时间
}
catch (Exception e)
{
System.out.println("Error: " + e);
}
}
}
}
// 过滤器,起数据处理作用,读取管道1中输入流的内容,并将其放到管道2的输出流中
class Filter extends Thread
{
private DataInputStream in;
private DataOutputStream out;
private double total = 0;
private int count = 0;
public Filter(InputStream is, OutputStream os)
{
in = new DataInputStream(is);
out = new DataOutputStream(os);
}
public void run()
{
while (true)
{
try
{
double x = in.readDouble(); // 读取流中的数据
total += x;
count++;
if (count != 0)
{
double d = total / count;
out.writeDouble(d); // 将得到的数据平均值写入流
}
}
catch (IOException e)
{
System.out.println("Error: " + e);
}
}
}
}
// 后端:读取管道2输入流的内容
class Consumer extends Thread
{
private double old_avg = 0;
private DataInputStream in;
public Consumer(InputStream is)
{
in = new DataInputStream(is);
}
public void run()
{
while (true)
{
try
{
double avg = in.readDouble();
if (Math.abs(avg - old_avg) > 0.01)
{
System.out.println(this.getName() + "--现在的平均值是: " + avg);
System.out.println();
old_avg = avg;
}
}
catch (IOException e)
{
System.out.println("Error: " + e);
}
}
}
}
管道字符流
Java利用 java.io.PipedWriter和java.io.PipedReader在线程之间传输字符信息。与 java.io.PipedOutputStream和java.io.PipedInputStream类似,java.io.PipedWriter 是java.io.Writer的直接子类,java.io.PipedReader是java.io.Reader的直接子类。PipedWriter拥有一个允许指定输入管道字符流的构造方法,而PipedReader拥有一个允许指定输出管道字符流的构造方法。从而使得PipedWriter和PipedReader往往成对出现、配合使用。