我最近发现了这个成语,我想知道是否有我遗失的东西。我从来没有见过它。我在野外工作的几乎所有Java代码都倾向于将数据压入字符串或缓冲区,而不是像这个例子(例如使用HttpClient和XML API):
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 Load / Save API将XML Document序列化为HTTP请求的内容。据我所知,它使用非常少的额外代码(仅Runnable
,PipedInputStream
和PipedOutputStream
的几行)最小化了内存的使用。
那么,这个成语有什么问题?如果这个成语没有任何问题,为什么我没有看到它?
编辑:澄清一下,PipedInputStream
和PipedOutputStream
取代了遍布各地的样板缓冲区副本,它们还允许您在写出已处理数据的同时处理传入数据。他们不使用操作系统管道。
答案 0 :(得分:44)
来自Javadocs:
通常,一个线程从PipedInputStream对象读取数据,而其他线程将数据写入相应的PipedOutputStream。建议不要尝试使用单个线程中的两个对象,因为它可能使线程死锁。
这可能部分解释了为什么它不常用。
我认为另一个原因是许多开发人员不了解其目的/利益。
答案 1 :(得分:7)
在你的例子中,你创建了两个线程来完成一个可以完成的工作。并将I / O延迟引入混合中。
你有更好的例子吗?或者我刚回答你的问题。
将一些评论(至少我对它们的看法)提取到主要回复中:
在特定示例中,管道流允许使用HttpClient提供的现有RequestEntity实现类。我认为更好的解决方案是创建一个新的实现类,如下所示,因为该示例最终是一个顺序操作,无法从并发实现的复杂性和开销中受益。虽然我将RequestEntity显示为匿名类,但可重用性表明它应该是一流的类。
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);
}
});
答案 2 :(得分:6)
我最近才发现了PipedInputStream / PipedOutputStream类。
我正在开发一个需要通过SSH在远程服务器上执行命令的Eclipse插件。我正在使用JSch,并且Channel API从输入流中读取并写入输出流。但我需要通过输入流提供命令并从输出流中读取响应。这就是PipedInput / OutputStream的用武之地。
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();
答案 3 :(得分:4)
此外,回到原始示例:不,它也没有完全最小化内存使用量。 DOM树被构建,内存中缓冲完成 - 虽然这比完整字节数组副本更好,但它并没有那么好。 但在这种情况下缓冲会更慢;并且还创建了一个额外的线程 - 您不能在单个线程中使用PipedInput / OutputStream对。
PipedXxxStreams有时会很有用,但之所以没有使用它们是因为它们通常不是正确的解决方案。它们可以用于线程间通信,这就是我用它们的价值所在。考虑到SOA如何将大多数此类边界推送到服务之间而不是线程之间,只是没有那么多用例。
答案 4 :(得分:2)
我尝试使用这些类一段时间后,我忘记了细节。但我确实发现他们的实施存在致命缺陷。我不记得它是什么,但我有一个偷偷摸摸的记忆,它可能是一个竞争条件,这意味着他们偶尔会陷入僵局(是的,我当然是在单独的线程中使用它们:它们根本不可用于单线程,并没有设计为)。
我可能会查看他们的源代码,看看我是否可以看到问题所在。
答案 5 :(得分:2)
这是管道有意义的用例:
假设您有第三方lib,例如xslt mapper或crypto lib,它具有如下界面:doSomething(inputStream,outputStream)。并且您不希望在通过电线发送之前缓冲结果。 Apache和其他客户端不允许直接访问线路输出流。最接近的是获取输出流 - 在写入标题后的偏移量中 - 在请求实体对象中。但由于这是在引擎盖下,所以仍然不足以将输入流和输出流传递给第三方lib。管道是解决这个问题的好方法。
顺便提一下,我写了一个Apache的HTTP客户端API [PipedApacheClientOutputStream]的反转,它使用Apache Commons HTTP Client 4.3.4为HTTP POST提供了一个OutputStream接口。这是Piped Streams可能有意义的示例。
答案 6 :(得分:1)
java.io管道有太多的上下文切换(每个字节读/写)和他们的java.nio对应要求你有一些NIO背景和正确使用的通道和东西,这是我自己的管道使用阻塞的实现单个生产者/消费者将快速执行和扩展的队列:
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;
}
}
);
}
答案 7 :(得分:0)
那么,这个成语有什么问题?如果 这个成语没有错, 为什么我没有看到它?
编辑:澄清,PipedInputStream和 PipedOutputStream取代了 样板缓冲区缓冲区复制 到处都是,他们也是 允许您处理传入的数据 同时写出来 处理过的数据他们不使用操作系统 管道
你已经陈述了它的作用,但没有说明你为什么要这样做。
如果您认为这会减少使用的资源(CPU /内存)或提高性能,那么它也不会这样做。但是它会使你的代码更复杂。
基本上你有一个没有问题的解决方案。
答案 8 :(得分:0)
PipedInputStream和PipeOutputStream阻塞等待另一端从已满或空缓冲区读取或写入数据时,它们将在 1秒内休眠其线程。不要使用。