转载请注明源出处:http://www.cnblogs.com/lighten/p/7056278.html

1.前言

  本章介绍Java的IO体系中最后一对字节流--管道流。之前在字节数组流的时候就说过,其可以充当输入输出流的转换作用,Java中还有一个管道流可以完成相似的功能,但是其主要作用是用于不同线程间的通讯,下面就具体讲一下管道流是如何实现的,以及相关例子。

  值得注意的是,在JDK源码注释中提到了,通常使用一个管道输出流关联一个管道输入流形成管道会话。通常输入流和输出流是不在一个线程中的,如果在同一个线程中使用,可能会造成死锁。当无法从输入流中读取数据的时候,输出流会被中断。

2.PipedOutputStream

  管道输出流十分简单,其继承OutputStream,具体类结构如下:

  其接受一个管道输入流,调用connect方法,判断通过后会重置相关数据并赋值。当然也可以直接new一个管道输出流,再通过connect方法关联。

  两个write方法都和输出流没有关系,是通过输入流来进行操作的。

  flush方法和close方法也是通过管道输入流进行操作的。

  整个输入流的操作,都是通过输入流完成的,所以接下来我们主要关注输入流做了什么。

3.PipedInputStream

  这个管道输入流的内容也不多:

  上面就是主要的内容了,输入流有一个buffer字节数组,构造函数有输出流的时候做了两件事情,一个是initPipe初始化这个buffer,默认大小1024个字节,另一个就是调用connect方法,connect方法实际上就是调用输出流的connect,反过来修改了输入流的相关字段。如果输入流构造时没有输出流,就需要使用connect方法进行关联。但是切记,不管是输入流初始化和输出流初始化,connect方法无论是通过什么途径,都只能调用一次,也就是两个对象相互关联只能发生一次。

  下面我们关注一下输出流write所调用输入流的receive都做了些什么:

  管道流输出的时候就会调用输入管道流的receive方法,最终写入buffer中。注意buffer写完了就会重置写入下标in。receive一个数组也是判断buffer剩余空间是否足够而已。read方法就是读取这个数组中的内容了:

3.1 死锁探究

   之前说了,如果写入和读取这两个操作是在同一线程可能会发生死锁,这里具体看下是如何死锁的,测试代码如下:

    @Test
public void test() throws IOException {
PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream(pis);
byte[] read = new byte[10];
byte[] write = new byte[100];
int count = 0;
while(true) {
pos.write(write);
pis.read(read);
System.out.println("完成写入读取次数:"+(++count));
}
}

  结果如下:

  计数到11的时候就不进行下去了,很显然死锁了。这个是怎么产生的呢?回顾上面源码:

  写入数据时,调用了receive方法,获得pis的对象锁。写完了数据就会释放锁了。read方法也需要获取锁,然后读完了就释放锁。乍一看好像没什么问题啊,不应该产生死锁的啊。但是问题出在write方法调用receive的一个方法awaitSpace上。因为写入比读取速度快,按照源代码的做法就会造成in的值追上out,然后就一直等待输入流使用,由于输入流和输出流在同一个线程,这里就变成了一个死循环了,wait之后依旧是在等写入消耗。写入比读取快也会造成wait。注释上说的死锁不知是否是这个样子,如果是那么说死锁就感觉有点不恰当了。

3.2 写入读取操作说明

  读写操作在不同线程的时候,数据从管道输出流中写入,调用管道输入流的receive方法。里面有一个缓存数组buffer,用于接收write方法写入的字节。输入流有两个下标,一个in用于标记当前缓存到的字节数,一个out用于标记read方法读取buffer的位置。写入的时候,如果in==out,则暂停写入,因为此时判断写入快过读取,防止数据被覆盖,这个时候写入线程就会挂起,等待读取线程读取数据。读取线程如果读到in==out则认为写入完成,读取也就完成了。最初in是等于-1的,所以第一次读取的时候,其也是一个挂起读取线程等待写入的过程。之后in只有在读取线程读完了所有写入的时候才会为-1。下面这段在read()方法中,只有第一次读取可能进入,因为如果有写入的时候in就不会小于0。

  问题来了,如果写入线程挂起了,读取线程读到目前写入位置,即in==out时,是判断读取完成的,这个时候会出现什么现象呢?

    @Test
public void test2() throws IOException, InterruptedException {
PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream(pis);
CountDownLatch latch = new CountDownLatch(2);
new Thread(new Runnable() {
@Override
public void run() {
byte[] b = new byte[1025];
b[1024] = 1;
try {
pos.write(b , 0, 1022);
Thread.sleep(2000);
pos.write(b, 1022, b.length);
pos.close();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}
}).start(); new Thread(new Runnable() {
@Override
public void run() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len = 0;
try {
byte b;
while((b = (byte) pis.read()) != -1) {
baos.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
byte[] result = baos.toByteArray();
System.out.println(result.length + ":" + Arrays.toString(result));
latch.countDown();
}
}).start();
latch.await();
}

  结果是:

  会出现异常,且只能读取到第一次写入的1022个字节。这就会产生一个问题,在实际使用的时候就会发生先写入一批数据,再写入一批数据,但是可能读取判断第一批结束了的尴尬情况。有没有什么好的方法解决这个问题呢?至少我没有找到什么好办法。使用管道流的时候最好一次写入,并且初始化管道流的buffer大小最好是写入的大小。其它的方法并不能完全防止这种现象发生,只是概率小一点而已,比如控制读写速度。如果真的要实现多次写入并且要可靠,我能想到的办法就只有,读写线程共用一个锁,再加一个流结束标志符了。但是这种方法也要万分小心,在开始很容易产生死锁,如果读线程先获取公共锁,内部又获取了管道流的锁,即便释放了管道流的锁,写线程也拿不到外层的公共锁。下面给一个demo,仅供参考,可能会有问题:

        static boolean end = false;
@Test
public void test3() throws IOException, InterruptedException {
PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream(pis);
Object monitor = new Object();
CountDownLatch latch = new CountDownLatch(2);
new Thread(new Runnable() {
@Override
public void run() {
int count = 3;
while(count-->0) {
synchronized (monitor) {
System.out.println(count);
byte[] b = new byte[1022];
try {
pos.write(b , 0, b.length);
Thread.sleep(2000);
pos.write(1);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
monitor.notifyAll();
try {
monitor.wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
end = true;
try {
pos.close();
} catch (IOException e) {
e.printStackTrace();
}
latch.countDown();
}
}).start(); new Thread(new Runnable() {
@Override
public void run() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len = 0;
try {
System.out.println("reading");
synchronized (monitor) {
while (!end) {
len = pis.read(buf);
System.out.println("reading...");
baos.write(buf, 0, len);
monitor.notifyAll();
try {
monitor.wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
byte[] result = baos.toByteArray();
System.out.println(result.length + ":" + Arrays.toString(result));
latch.countDown();
}
}).start();
latch.await();
}

  再次声明,此代码没有经过仔细思考,只是提供一个思路,出现问题概不负责(确实有些问题),使用管道流最好还是一次性写入所有数据比较好。

  

Java之IO(八)PipedIutputStream和PipedOutputStream的更多相关文章

  1. Java之IO(零)总结

    转载请注明原出处:http://www.cnblogs.com/lighten/p/7274378.html 1.前言 本章是对之前所讲述的整个Java的IO包的一个总结,抽出个人认为比较重要的知识点 ...

  2. 彻底明白Java的IO系统

    java学习:彻底明白Java的IO系统 文章来源:互联网 一. Input和Output1. stream代表的是任何有能力产出数据的数据源,或是任何有能力接收数据的接收源.在Java的IO中,所有 ...

  3. Java之IO流详解

    IO流 Input/Output 完成输入/输出 应用程序运行时——数据在内存中  ←→ 把数据写入硬盘(磁带)  内存中的数据不可持久保存的  输入:从外部存储器(硬盘.磁带.U盘)把数据读入内存. ...

  4. Java面向对象 IO (四)

     Java面向对象  IO  (四) 知识概要:                 (1)打印流 (2)序列流 SequenceInputStream (3)ObjectInputStream与Ob ...

  5. Java的IO系统

     Java IO系统     "对语言设计人员来说,创建好的输入/输出系统是一项特别困难的任务."     由于存在大量不同的设计方案,所以该任务的困难性是很容易证明的.其中最大的 ...

  6. Java知IO

    ---恢复内容开始--- Java将IO(文件.网络.终端)封装成非常多的类,看似繁杂,其实每个类的具有独特的功能. 按照存取的对象是二进制还是文本,java使用字节流和字符流实现IO. 流是java ...

  7. java的Io流学习

    Java中io流的学习(一)File:https://blog.csdn.net/qq_41061437/article/details/81672859 Java中io流的学习(二)FileInpu ...

  8. Java的IO文档

    1.     File类 1.1. File类说明 存储在变量,数组和对象中的数据是暂时的,当程序终止时他们就会丢失.为了能够永 久的保存程序中创建的数据,需要将他们存储到硬盘或光盘的文件中.这些文件 ...

  9. 【Java】IO Stream详细解读

    成鹏致远 | 2013年12月31日 什么是IO Java中I/O操作主要是指使用Java进行输入,输出操作. Java所有的I/O机制都是基于数据流进行输入输出,这些数据流表示了字符或者字节数据的流 ...

随机推荐

  1. web页面中a标签下载文件包含中文下载失败的解决

    之前用到的文件下载,文件名都是时间戳的形式或者英文名.下载没有问题.后来附件有中文后写在页面是下面效果,点击下载,下载失败. 对应链接拿出来.是如下效果 之前用了各种其他办法都不理想,比如转义什么的. ...

  2. UVaLive 4452 The Ministers' Major Mess (TwoSat)

    题意:有 m 个人对 n 个方案投票,每个人最多只能对其中的4个方案投票(其他的相当于弃权),每一票要么支持要么反对.问是否存在一个最终决定,使得每个投票人都有超过一半的建议被采纳,在所有可能的最终决 ...

  3. UVa 11992 Fast Matrix Operations (线段树,区间修改)

    题意:给出一个row*col的全0矩阵,有三种操作 1 x1 y1 x2 y2 v:将x1 <= row <= x2, y1 <= col <= y2里面的点全部增加v: 2 ...

  4. C语言 fread()与fwrite()函数说明与示例

    1.作用 读写文件数据块. 2.函数原型 (1)size_t fread ( void * ptr, size_t size, size_t count, FILE * stream ); 其中,pt ...

  5. 团队项目第六周——Alpha阶段项目复审(盐酸队)

    Alpha阶段项目复审 小组 优点 缺点,bug报告 名次 天冷记得穿秋裤队 功能比较新颖,可以离线下载,做的比较完整 在下载电影时容易中断 1 只会嘤嘤嘤队 游戏和记单词的融合,也比较新颖 部分浏览 ...

  6. Java代码优化(一)

    前言 2016年3月修改,结合自己的工作和平时学习的体验重新谈一下为什么要进行代码优化.在修改之前,我的说法是这样的: 就像鲸鱼吃虾米一样,也许吃一个两个虾米对于鲸鱼来说作用不大,但是吃的虾米多了,鲸 ...

  7. JS——图片预览功能

    <script type="text/javascript">    function DisplayImage(fileTag) {        document. ...

  8. 窗口与导航-----Selenium快速入门(十三)

    前面所讲的,大部分是WebDriver这个接口以及相关的类的使用.而本文所讲的窗口与导航,也是里面的内容,而且非常简单,目测就能学会. 一.窗口,也就是window,这里的窗口是指浏览器窗口.他的方法 ...

  9. NET Core2.1 WEB老项目迁移

    .NET Core2.1 版本新增功能不在赘述. NET Core2.1更新链接 如果开发需要安装Net Core2.1SDK,及Runtime. .NET Core2.1安装地址. 接下来是WEB ...

  10. uwp ListView列表滑动特效

    在看过一篇文章 WPF自定义控件之列表滑动特效 PowerListBox  http://www.cnblogs.com/ShenNan/p/4993374.html#3619585 实现了滑动的特效 ...