Why doesn't more Java code use PipedInputStream / PipedOutputStream?
我最近发现了这个成语,我想知道我是否遗漏了一些东西。我从没见过它用过。我在野外工作的几乎所有Java代码都支持将数据拖到字符串或缓冲区中,而不是像这个例子(例如使用HTTPclipse和XML API):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | final LSOutput output; // XML stuff initialized elsewhere final LSSerializer serializer; final Document doc; // ... PostMethod post; // HttpClient post request final PipedOutputStream source = new PipedOutputStream(); PipedInputStream sink = new PipedInputStream(source); // ... executor.execute(new Runnable() { public void run() { output.setByteStream(source); serializer.write(doc, output); try { source.close(); } catch (IOException e) { throw new RuntimeException(e); } }}); post.setRequestEntity(new InputStreamRequestEntity(sink)); int status = httpClient.executeMethod(post); |
该代码使用Unix管道样式技术来防止将XML数据的多个副本保存在内存中。它使用HTTP Post输出流和DOM加载/保存API将XML文档序列化为HTTP请求的内容。据我所知,它将内存的使用降到最低,只需要很少的额外代码(仅限于用于
那么,这个成语怎么了?如果这个成语没什么问题,为什么我没看过呢?
编辑:为了澄清,
来自javadocs:
Typically, data is read from a PipedInputStream object by one thread and data is written to the corresponding PipedOutputStream by some other thread. Attempting to use both objects from a single thread is not recommended, as it may deadlock the thread.
号
这可以部分解释为什么它不常用。
我假设另一个原因是许多开发人员不理解它的目的/好处。
在您的示例中,您创建了两个线程来完成可以由一个线程完成的工作。并在混合中引入I/O延迟。
你有更好的例子吗?或者我刚刚回答了你的问题。
要将一些评论(至少我对它们的看法)纳入主要回应:
- 并发性给应用程序带来了复杂性。现在,您必须关注独立数据流的排序,而不是处理单一的线性数据流。在某些情况下,增加的复杂性可能是合理的,特别是如果您可以利用多个核心/CPU来完成CPU密集型工作。
- 如果您所处的环境可以从并发操作中获益,那么通常有更好的方法来协调线程之间的数据流。例如,使用并发队列在线程之间传递对象,而不是在对象流中包装管道流。
- 如果一个管道流可能是一个很好的解决方案,那就是当您有多个线程执行文本处理时,一个la a unix管道(例如:grep sort)。
在特定示例中,管道流允许使用httpclient提供的现有requestEntity实现类。我认为一个更好的解决方案是创建一个新的实现类,如下所示,因为该示例最终是一个顺序操作,不能从并发实现的复杂性和开销中获益。当我将requestEntity显示为匿名类时,可重用性将指示它应该是一个一流的类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | post.setRequestEntity(new RequestEntity() { public long getContentLength() { return 0-1; } public String getContentType() { return"text/xml"; } public boolean isRepeatable() { return false; } public void writeRequest(OutputStream out) throws IOException { output.setByteStream(out); serializer.write(doc, output); } }); |
我最近也只发现了pipedinputstream/pipedOutputstream类。
我正在开发一个Eclipse插件,它需要通过ssh在远程服务器上执行命令。我使用的是JSCH,通道API从输入流读取数据并写入输出流。但我需要通过输入流输入命令并从输出流读取响应。这就是pipedinput/outputstream进入的地方。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import java.io.PipedInputStream; import java.io.PipedOutputStream; import com.jcraft.jsch.Channel; Channel channel; PipedInputStream channelInputStream = new PipedInputStream(); PipedOutputStream channelOutputStream = new PipedOutputStream(); channel.setInputStream(new PipedInputStream(this.channelOutputStream)); channel.setOutputStream(new PipedOutputStream(this.channelInputStream)); channel.connect(); // Write to channelInputStream // Read from channelInputStream channel.disconnect(); |
号
另外,回到原来的例子:不,它也不完全最小化内存使用。构建了DOM树,内存缓冲完成了——虽然这比全字节数组副本更好,但也没有那么好。但是,在这种情况下,缓冲会变慢;并且还会创建一个额外的线程——您不能在单个线程中使用pipedinput/outputstream对。
有时候pipedxxx流是有用的,但是它们没有被更多地使用的原因是它们通常不是正确的解决方案。它们对于线程间的通信是可以的,这就是我使用它们的价值所在。考虑到SOA是如何将大多数这样的边界推到服务之间,而不是线程之间的,这只是因为没有那么多的用例。
下面是一个管道有意义的用例:
假设您有一个第三方库,例如具有如下接口的XSLT映射器或加密库:dosomething(inputstream、outputstream)。你不想在发送之前缓冲结果。Apache和其他客户机不允许直接访问线输出流。最接近的方法是在请求实体对象中写入头之后,在偏移量处获取输出流。但由于这是在幕后进行的,所以还不足以将输入流和输出流传递给第三方库。管道是解决这个问题的好办法。
顺便说一句,我写了一个Apache的HTTP客户机API[PipedApacheClientOutputstream]的反转,它使用Apache Commons HTTP客户机4.3.4为HTTP Post提供了一个输出流接口。这是一个管道流可能有意义的例子。
我曾经尝试过用这些类来做一些事情,我忘记了细节。但我发现它们的实现存在致命的缺陷。我不记得它是什么,但我有一个潜移默化的记忆,它可能是一个竞争条件,这意味着它们偶尔会死锁(是的,当然我是在单独的线程中使用它们:它们在单个线程中根本不可用,而且不是设计为可用的)。
我可能会看看他们的源代码,看看我是否能看到问题可能是什么。
java.io管道有太多的上下文切换(每字节读/写),它们的java.nio对应项要求您具有一些nio背景和通道和内容的正确使用,这是我自己使用阻塞队列实现的管道,对于单个生产商/消费者来说,它将执行得很快并具有很好的伸缩性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | import java.io.IOException; import java.io.OutputStream; import java.util.concurrent.*; public class QueueOutputStream extends OutputStream { private static final int DEFAULT_BUFFER_SIZE=1024; private static final byte[] END_SIGNAL=new byte[]{}; private final BlockingQueue<byte[]> queue=new LinkedBlockingDeque<>(); private final byte[] buffer; private boolean closed=false; private int count=0; public QueueOutputStream() { this(DEFAULT_BUFFER_SIZE); } public QueueOutputStream(final int bufferSize) { if(bufferSize<=0){ throw new IllegalArgumentException("Buffer size <= 0"); } this.buffer=new byte[bufferSize]; } private synchronized void flushBuffer() { if(count>0){ final byte[] copy=new byte[count]; System.arraycopy(buffer,0,copy,0,count); queue.offer(copy); count=0; } } @Override public synchronized void write(final int b) throws IOException { if(closed){ throw new IllegalStateException("Stream is closed"); } if(count>=buffer.length){ flushBuffer(); } buffer[count++]=(byte)b; } @Override public synchronized void write(final byte[] b, final int off, final int len) throws IOException { super.write(b,off,len); } @Override public synchronized void close() throws IOException { flushBuffer(); queue.offer(END_SIGNAL); closed=true; } public Future<Void> asyncSendToOutputStream(final ExecutorService executor, final OutputStream outputStream) { return executor.submit( new Callable<Void>() { @Override public Void call() throws Exception { try{ byte[] buffer=queue.take(); while(buffer!=END_SIGNAL){ outputStream.write(buffer); buffer=queue.take(); } outputStream.flush(); } catch(Exception e){ close(); throw e; } finally{ outputStream.close(); } return null; } } ); } |
So, what's wrong with this idiom? If
there's nothing wrong with this idiom,
why haven't I seen it?EDIT: to clarify, PipedInputStream and
PipedOutputStream replace the
boilerplate buffer-by-buffer copy that
shows up everywhere, and they also
allow you to process incoming data
concurrently with writing out the
processed data. They don't use OS
pipes.
号
你已经说明了它的作用,但没有说明你为什么要这样做。
如果您认为这将减少使用的资源(CPU/内存)或提高性能,那么它也不会做到这一点。但是,它会使代码更加复杂。
基本上,你有一个没有问题的解决方案。