我在开发阶段,在那里我有两个模块,从一个我得到输出作为一个OutputStream和第二个,它只接受InputStream。你知道如何将OutputStream转换为InputStream(反之亦然,我的意思是真的这样),我将能够连接这两个部分吗?

谢谢


当前回答

正如一些人已经回答的那样,没有有效的方法来“转换”一个OutputStream到一个InputStream。解决这类问题的技巧是将所有需要OutputStream的代码执行到它自己的线程中。通过使用管道流,我们可以将数据从创建的线程传输到InputStream中。

使用示例:

public static InputStream downloadFileAsStream(final String uriString) throws IOException {
        final InputStream inputStream = runInOwnThreadWithPipedStreams((outputStream) -> {
            try {
                downloadUriToStream(uriString, outputStream);
            } catch (final Exception e) {
                LOGGER.error("Download of uri '{}' has failed", uriString, e);
            }
        });
        return inputStream;
    }

辅助功能:

public static InputStream runInOwnThreadWithPipedStreams(
            final Consumer<OutputStream> outputStreamConsumer) throws IOException {
        final PipedInputStream inputStream = new PipedInputStream();
        final PipedOutputStream outputStream = new PipedOutputStream(inputStream);
        new Thread(new Runnable() {
            public void run() {
                try {
                    outputStreamConsumer.accept(outputStream);
                } finally {
                    try {
                        outputStream.close();
                    } catch (final IOException e) {
                        LOGGER.error("Closing outputStream has failed. ", e);
                    }
                }
            }
        }).start();
        return inputStream;
    }

单元测试:

@Test
void testRunInOwnThreadWithPipedStreams() throws IOException {

    final InputStream inputStream = LoadFileUtil.runInOwnThreadWithPipedStreams((OutputStream outputStream) -> {
        try {
            IOUtils.copy(IOUtils.toInputStream("Hello World", StandardCharsets.UTF_8), outputStream);
        } catch (final IOException e) {
            LoggerFactory.getLogger(LoadFileUtilTest.class).error(e.getMessage(), e);
        }
    });

    final String actualResult = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
    Assertions.assertEquals("Hello World", actualResult);
}

其他回答

如果你想从一个InputStream生成一个OutputStream,有一个基本的问题。写入OutputStream的方法会阻塞,直到完成为止。因此,当编写方法完成时,结果是可用的。这有两个后果:

如果只使用一个线程,则需要等待所有内容写入(因此需要将流数据存储在内存或磁盘中)。 如果希望在数据完成之前访问数据,则需要第二个线程。

变体1可以使用字节数组或字段实现。 变体1可以使用pipies实现(直接或额外的抽象-例如RingBuffer或来自其他注释的谷歌库)。

事实上,在标准java中,没有其他方法可以解决这个问题。每个解决方案都是其中一个的实现。

有一个概念叫做“延续”(详见维基百科)。在这种情况下,这基本上意味着:

有一个特殊的输出流,它需要一定数量的数据 如果达到数量,则流将控制权交给对应的特殊输入流 输入流在读取数据之前提供可用的数据量,在读取之后,它将控制传递回输出流

虽然有些语言内置了这个概念,但对于java,您需要一些“魔法”。例如apache中的“commons-javaflow”实现了这样的java。缺点是这需要在构建时进行一些特殊的字节码修改。因此,将所有的东西都放在一个带有自定义构建脚本的额外库中是有意义的。

easystream开源库直接支持将OutputStream转换为InputStream: http://io-tools.sourceforge.net/easystream/tutorial/tutorial.html

// create conversion
final OutputStreamToInputStream<Void> out = new OutputStreamToInputStream<Void>() {
    @Override
    protected Void doRead(final InputStream in) throws Exception {
           LibraryClass2.processDataFromInputStream(in);
           return null;
        }
    };
try {   
     LibraryClass1.writeDataToTheOutputStream(out);
} finally {
     // don't miss the close (or a thread would not terminate correctly).
     out.close();
}

他们还列出了其他选项:http://io-tools.sourceforge.net/easystream/outputstream_to_inputstream/implementations.html

Write the data the data into a memory buffer (ByteArrayOutputStream) get the byteArray and read it again with a ByteArrayInputStream. This is the best approach if you're sure your data fits into memory. Copy your data to a temporary file and read it back. Use pipes: this is the best approach both for memory usage and speed (you can take full advantage of the multi-core processors) and also the standard solution offered by Sun. Use InputStreamFromOutputStream and OutputStreamToInputStream from the easystream library.

你需要一个中间类来缓冲。每次调用InputStream.read(byte[]…)时,缓冲类将用从OutputStream.write(byte[]…)传入的下一个块填充传入的字节数组。由于块的大小可能不相同,适配器类需要存储一定数量的块,直到它有足够的容量填满读缓冲区和/或能够存储任何缓冲区溢出。

这篇文章很好地分解了解决这个问题的几种不同方法:

http://blog.ostermiller.org/convert-java-outputstream-inputstream

我遇到了同样的问题,将ByteArrayOutputStream转换为ByteArrayInputStream,并通过使用ByteArrayOutputStream的派生类来解决它,它能够返回一个ByteArrayInputStream,该ByteArrayInputStream是由ByteArrayOutputStream的内部缓冲区初始化的。这种方式不会使用额外的内存,而且“转换”非常快:

package info.whitebyte.utils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

/**
 * This class extends the ByteArrayOutputStream by 
 * providing a method that returns a new ByteArrayInputStream
 * which uses the internal byte array buffer. This buffer
 * is not copied, so no additional memory is used. After
 * creating the ByteArrayInputStream the instance of the
 * ByteArrayInOutStream can not be used anymore.
 * <p>
 * The ByteArrayInputStream can be retrieved using <code>getInputStream()</code>.
 * @author Nick Russler
 */
public class ByteArrayInOutStream extends ByteArrayOutputStream {
    /**
     * Creates a new ByteArrayInOutStream. The buffer capacity is
     * initially 32 bytes, though its size increases if necessary.
     */
    public ByteArrayInOutStream() {
        super();
    }

    /**
     * Creates a new ByteArrayInOutStream, with a buffer capacity of
     * the specified size, in bytes.
     *
     * @param   size   the initial size.
     * @exception  IllegalArgumentException if size is negative.
     */
    public ByteArrayInOutStream(int size) {
        super(size);
    }

    /**
     * Creates a new ByteArrayInputStream that uses the internal byte array buffer 
     * of this ByteArrayInOutStream instance as its buffer array. The initial value 
     * of pos is set to zero and the initial value of count is the number of bytes 
     * that can be read from the byte array. The buffer array is not copied. This 
     * instance of ByteArrayInOutStream can not be used anymore after calling this
     * method.
     * @return the ByteArrayInputStream instance
     */
    public ByteArrayInputStream getInputStream() {
        // create new ByteArrayInputStream that respects the current count
        ByteArrayInputStream in = new ByteArrayInputStream(this.buf, 0, this.count);

        // set the buffer of the ByteArrayOutputStream 
        // to null so it can't be altered anymore
        this.buf = null;

        return in;
    }
}

我把这些东西放在github: https://github.com/nickrussler/ByteArrayInOutStream上

似乎有许多链接和其他类似的东西,但没有使用管道的实际代码。使用java.io.PipedInputStream和java.io.PipedOutputStream的优点是不会额外消耗内存。ByteArrayOutputStream.toByteArray()返回原始缓冲区的副本,因此这意味着无论内存中有什么,现在都有它的两个副本。然后写入InputStream意味着现在有了数据的三个副本。

使用lambdas的代码(从评论中向@John Manko致敬):

PipedInputStream in = new PipedInputStream();
final PipedOutputStream out = new PipedOutputStream(in);
// in a background thread, write the given output stream to the
// PipedOutputStream for consumption
new Thread(() -> {originalOutputStream.writeTo(out);}).start();

@John Manko注意到的一件事是,在某些情况下,当您无法控制OutputStream的创建时,您可能会在创建者过早地清理OutputStream对象的情况下结束。如果你正在获取ClosedPipeException,那么你应该尝试反向构造函数:

PipedInputStream in = new PipedInputStream(out);
new Thread(() -> {originalOutputStream.writeTo(out);}).start();

注意,您也可以为下面的示例反转构造函数。

也感谢@AlexK纠正我开始一个线程,而不是仅仅启动一个Runnable。


使用try-with-resources的代码:

// take the copy of the stream and re-write it to an InputStream
PipedInputStream in = new PipedInputStream();
    new Thread(new Runnable() {
        public void run () {
            // try-with-resources here
            // putting the try block outside the Thread will cause the
            // PipedOutputStream resource to close before the Runnable finishes
            try (final PipedOutputStream out = new PipedOutputStream(in)) {
                // write the original OutputStream to the PipedOutputStream
                // note that in order for the below method to work, you need
                // to ensure that the data has finished writing to the
                // ByteArrayOutputStream
                originalByteArrayOutputStream.writeTo(out);
            }
            catch (IOException e) {
                // logging and exception handling should go here
            }
        }
    }).start();

我写的原始代码:

// take the copy of the stream and re-write it to an InputStream
PipedInputStream in = new PipedInputStream();
final PipedOutputStream out = new PipedOutputStream(in);
new Thread(new Runnable() {
    public void run () {
        try {
            // write the original OutputStream to the PipedOutputStream
            // note that in order for the below method to work, you need
            // to ensure that the data has finished writing to the
            // ByteArrayOutputStream
            originalByteArrayOutputStream.writeTo(out);
        }
        catch (IOException e) {
            // logging and exception handling should go here
        }
        finally {
            // close the PipedOutputStream here because we're done writing data
            // once this thread has completed its run
            if (out != null) {
                // close the PipedOutputStream cleanly
                out.close();
            }
        }   
    }
}).start();

这段代码假设原始ByteArrayOutputStream是一个ByteArrayOutputStream,因为它通常是唯一可用的输出流,除非您正在写入文件。这样做的好处是,因为它在一个单独的线程中,所以它也是并行工作的,所以无论消耗你的输入流的是什么,它也会从你的旧输出流中流出。这是有益的,因为缓冲区可以保持更小,您将有更少的延迟和内存使用。

如果你没有ByteArrayOutputStream,那么你将不得不使用java.io.OutputStream类中的write()方法之一或子类中可用的其他方法之一,而不是使用writeTo()。