关于java:如何将OutputStream转换为InputStream?

How to convert OutputStream to InputStream?

我正处于开发阶段,我有两个模块,其中一个模块的输出为OutputStream,第二个模块的输出仅接受InputStream。你知道如何将OutputStream转换为InputStream吗(我的意思是这样的,不是相反的),我可以把这两个部分连接起来?

谢谢


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

代码:

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
// 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();

此代码假定originalByteArrayOutputStreamByteArrayOutputStream,因为它通常是唯一可用的输出流,除非您正在写入文件。希望这有帮助!这一点的好处在于,由于它在一个单独的线程中,所以它也在并行工作,所以任何消耗输入流的东西都将从旧的输出流中流出。这是有益的,因为缓冲区可以保持更小,您的延迟和内存使用也会更少。


OutputStream是向其中写入数据的。如果某个模块暴露了一个OutputStream,则期望在另一端有一些读数。

另一方面,暴露InputStream的东西表明您需要监听这个流,并且您可以读取数据。

因此可以将InputStream连接到OutputStream上。

InputStream----read---> intermediateBytes[n] ----write----> OutputStream

正如有人所说,这就是ioutils中的copy()方法所允许的。走另一条路是没有意义的…希望这有点道理

更新:

当然,我越是想到这一点,我就越能看出这实际上是一个要求。我知道一些评论提到了Piped输入/输出流,但还有另一种可能性。

如果公开的输出流是一个ByteArrayOutputStream,那么您总是可以通过调用toByteArray()方法来获取全部内容。然后,可以使用ByteArrayInputStream子类创建输入流包装器。这两个都是伪流,基本上都只是包装一个字节数组。因此,以这种方式使用溪流在技术上是可能的,但对我来说,这仍然是非常奇怪的…


由于输入和输出流只是起点和终点,所以解决方案是在字节数组中临时存储数据。因此,您必须创建中间ByteArrayOutputStream,从中创建byte[],用作新ByteArrayInputStream的输入。

1
2
3
4
5
6
7
8
public void doTwoThingsWithStream(InputStream inStream, OutputStream outStream){
  //create temporary bayte array output stream
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
  doFirstThing(inStream, baos);
  //create input stream from baos
  InputStream isFromFirstData = new ByteArrayInputStream(baos.toByteArray());
  doSecondThing(isFromFirstData, outStream);
}

希望它有帮助。


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

本文详细介绍了解决此问题的几种不同方法:

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


1
2
3
ByteArrayOutputStream buffer = (ByteArrayOutputStream) aOutputStream;
byte[] bytes = buffer.toByteArray();
InputStream inputStream = new ByteArrayInputStream(bytes);


EasyStream开源库直接支持将输出流转换为输入流:http://io-tools.sourceforge.net/eaystream/tutorial/tutorial.html

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


我在将ByteArrayOutputStream转换为ByteArrayInputStream时遇到了同样的问题,并通过使用ByteArrayOutputStream的派生类解决了这个问题,该类能够返回用ByteArrayOutputStream的内部缓冲区初始化的ByteArrayInputStream。这样就不需要额外的内存,而且"转换"非常快:

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
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 <wyn>getInputStream()</wyn>.
 * @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


库IO附加程序可能有用。例如,如果要使用GZIPOutputStreamgzip一个InputStream并希望它同步发生(使用默认缓冲区大小8192):

1
2
InputStream is = ...
InputStream gz = IOUtil.pipe(is, o -> new GZIPOutputStream(o));

请注意,库有100%的单元测试覆盖率(当然,这是值得的!)在马文中心。Maven的依赖关系是:

1
2
3
4
5
<dependency>
  <groupId>com.github.davidmoten</groupId>
  io-extras</artifactId>
  <version>0.1</version>
</dependency>

一定要检查更高版本。


从我的角度来看,java.io.pipedinputstream/java.io.pipedoutsptream是考虑的最佳选择。在某些情况下,您可能希望使用bytearrayinputstream/bytearrayinputstream。问题是您需要复制缓冲区来将BytearrayOutputStream转换为BytearrayOutputStream。此外,BytearrayOutpustream/BytearrayInputstream限制为2GB。这里是一个OutPoSt/EndoSt流实现,我写的是绕过BytayRayOuttStudio/BytErayRePixStand限制(Scala代码,但对于Java开发人员来说是容易理解的):

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
import java.io.{IOException, InputStream, OutputStream}

import scala.annotation.tailrec

/** Acts as a replacement for ByteArrayOutputStream
  *
  */

class HugeMemoryOutputStream(capacity: Long) extends OutputStream {
  private val PAGE_SIZE: Int = 1024000
  private val ALLOC_STEP: Int = 1024

  /** Pages array
    *
    */

  private var streamBuffers: Array[Array[Byte]] = Array.empty[Array[Byte]]

  /** Allocated pages count
    *
    */

  private var pageCount: Int = 0

  /** Allocated bytes count
    *
    */

  private var allocatedBytes: Long = 0

  /** Current position in stream
    *
    */

  private var position: Long = 0

  /** Stream length
    *
    */

  private var length: Long = 0

  allocSpaceIfNeeded(capacity)

  /** Gets page count based on given length
    *
    * @param length   Buffer length
    * @return         Page count to hold the specified amount of data
    */

  private def getPageCount(length: Long) = {
    var pageCount = (length / PAGE_SIZE).toInt + 1

    if ((length % PAGE_SIZE) == 0) {
      pageCount -= 1
    }

    pageCount
  }

  /** Extends pages array
    *
    */

  private def extendPages(): Unit = {
    if (streamBuffers.isEmpty) {
      streamBuffers = new Array[Array[Byte]](ALLOC_STEP)
    }
    else {
      val newStreamBuffers = new Array[Array[Byte]](streamBuffers.length + ALLOC_STEP)
      Array.copy(streamBuffers, 0, newStreamBuffers, 0, streamBuffers.length)
      streamBuffers = newStreamBuffers
    }

    pageCount = streamBuffers.length
  }

  /** Ensures buffers are bug enough to hold specified amount of data
    *
    * @param value  Amount of data
    */

  private def allocSpaceIfNeeded(value: Long): Unit = {
    @tailrec
    def allocSpaceIfNeededIter(value: Long): Unit = {
      val currentPageCount = getPageCount(allocatedBytes)
      val neededPageCount = getPageCount(value)

      if (currentPageCount < neededPageCount) {
        if (currentPageCount == pageCount) extendPages()

        streamBuffers(currentPageCount) = new Array[Byte](PAGE_SIZE)
        allocatedBytes = (currentPageCount + 1).toLong * PAGE_SIZE

        allocSpaceIfNeededIter(value)
      }
    }

    if (value < 0) throw new Error("AllocSpaceIfNeeded < 0")
    if (value > 0) {
      allocSpaceIfNeededIter(value)

      length = Math.max(value, length)
      if (position > length) position = length
    }
  }

  /**
    * Writes the specified byte to this output stream. The general
    * contract for <wyn>write</wyn> is that one byte is written
    * to the output stream. The byte to be written is the eight
    * low-order bits of the argument <wyn>b</wyn>. The 24
    * high-order bits of <wyn>b</wyn> are ignored.
    * <p>

    * Subclasses of <wyn>OutputStream</wyn> must provide an
    * implementation for this method.
    *
    * @param      b the <wyn>byte</wyn>.
    */

  @throws[IOException]
  override def write(b: Int): Unit = {
    val buffer: Array[Byte] = new Array[Byte](1)

    buffer(0) = b.toByte

    write(buffer)
  }

  /**
    * Writes <wyn>len</wyn> bytes from the specified byte array
    * starting at offset <wyn>off</wyn> to this output stream.
    * The general contract for <wyn>write(b, off, len)</wyn> is that
    * some of the bytes in the array <wyn>b</wyn> are written to the
    * output stream in order; element <wyn>b[off]</wyn> is the first
    * byte written and <wyn>b[off+len-1]</wyn> is the last byte written
    * by this operation.
    * <p>

    * The <wyn>write</wyn> method of <wyn>OutputStream</wyn> calls
    * the write method of one argument on each of the bytes to be
    * written out. Subclasses are encouraged to override this method and
    * provide a more efficient implementation.
    * <p>

    * If <wyn>b</wyn> is <wyn>null</wyn>, a
    * <wyn>NullPointerException</wyn> is thrown.
    * <p>

    * If <wyn>off</wyn> is negative, or <wyn>len</wyn> is negative, or
    * <wyn>off+len</wyn> is greater than the length of the array
    * <wyn>b</wyn>, then an <tt>IndexOutOfBoundsException</tt> is thrown.
    *
    * @param      b   the data.
    * @param      off the start offset in the data.
    * @param      len the number of bytes to write.
    */

  @throws[IOException]
  override def write(b: Array[Byte], off: Int, len: Int): Unit = {
    @tailrec
    def writeIter(b: Array[Byte], off: Int, len: Int): Unit = {
      val currentPage: Int = (position / PAGE_SIZE).toInt
      val currentOffset: Int = (position % PAGE_SIZE).toInt

      if (len != 0) {
        val currentLength: Int = Math.min(PAGE_SIZE - currentOffset, len)
        Array.copy(b, off, streamBuffers(currentPage), currentOffset, currentLength)

        position += currentLength

        writeIter(b, off + currentLength, len - currentLength)
      }
    }

    allocSpaceIfNeeded(position + len)
    writeIter(b, off, len)
  }

  /** Gets an InputStream that points to HugeMemoryOutputStream buffer
    *
    * @return InputStream
    */

  def asInputStream(): InputStream = {
    new HugeMemoryInputStream(streamBuffers, length)
  }

  private class HugeMemoryInputStream(streamBuffers: Array[Array[Byte]], val length: Long) extends InputStream {
    /** Current position in stream
      *
      */

    private var position: Long = 0

    /**
      * Reads the next byte of data from the input stream. The value byte is
      * returned as an <wyn>int</wyn> in the range <wyn>0</wyn> to
      * <wyn>255</wyn>. If no byte is available because the end of the stream
      * has been reached, the value <wyn>-1</wyn> is returned. This method
      * blocks until input data is available, the end of the stream is detected,
      * or an exception is thrown.
      *
      * <p>
 A subclass must provide an implementation of this method.
      *
      * @return the next byte of data, or <wyn>-1</wyn> if the end of the
      *         stream is reached.
      */

    @throws[IOException]
    def read: Int = {
      val buffer: Array[Byte] = new Array[Byte](1)

      if (read(buffer) == 0) throw new Error("End of stream")
      else buffer(0)
    }

    /**
      * Reads up to <wyn>len</wyn> bytes of data from the input stream into
      * an array of bytes.  An attempt is made to read as many as
      * <wyn>len</wyn> bytes, but a smaller number may be read.
      * The number of bytes actually read is returned as an integer.
      *
      * <p>
 This method blocks until input data is available, end of file is
      * detected, or an exception is thrown.
      *
      * <p>
 If <wyn>len</wyn> is zero, then no bytes are read and
      * <wyn>0</wyn> is returned; otherwise, there is an attempt to read at
      * least one byte. If no byte is available because the stream is at end of
      * file, the value <wyn>-1</wyn> is returned; otherwise, at least one
      * byte is read and stored into <wyn>b</wyn>.
      *
      * <p>
 The first byte read is stored into element <wyn>b[off]</wyn>, the
      * next one into <wyn>b[off+1]</wyn>, and so on. The number of bytes read
      * is, at most, equal to <wyn>len</wyn>. Let k be the number of
      * bytes actually read; these bytes will be stored in elements
      * <wyn>b[off]</wyn> through <wyn>b[off+</wyn>k<wyn>-1]</wyn>,
      * leaving elements <wyn>b[off+</wyn>k<wyn>]</wyn> through
      * <wyn>b[off+len-1]</wyn> unaffected.
      *
      * <p>
 In every case, elements <wyn>b[0]</wyn> through
      * <wyn>b[off]</wyn> and elements <wyn>b[off+len]</wyn> through
      * <wyn>b[b.length-1]</wyn> are unaffected.
      *
      * <p>
 The <wyn>read(b,</wyn> <wyn>off,</wyn> <wyn>len)</wyn> method
      * for class <wyn>InputStream</wyn> simply calls the method
      * <wyn>read()</wyn> repeatedly. If the first such call results in an
      * <wyn>IOException</wyn>, that exception is returned from the call to
      * the <wyn>read(b,</wyn> <wyn>off,</wyn> <wyn>len)</wyn> method.  If
      * any subsequent call to <wyn>read()</wyn> results in a
      * <wyn>IOException</wyn>, the exception is caught and treated as if it
      * were end of file; the bytes read up to that point are stored into
      * <wyn>b</wyn> and the number of bytes read before the exception
      * occurred is returned. The default implementation of this method blocks
      * until the requested amount of input data <wyn>len</wyn> has been read,
      * end of file is detected, or an exception is thrown. Subclasses are encouraged
      * to provide a more efficient implementation of this method.
      *
      * @param      b   the buffer into which the data is read.
      * @param      off the start offset in array <wyn>b</wyn>
      *                 at which the data is written.
      * @param      len the maximum number of bytes to read.
      * @return the total number of bytes read into the buffer, or
      *         <wyn>-1</wyn> if there is no more data because the end of
      *         the stream has been reached.
      * @see java.io.InputStream#read()
      */

    @throws[IOException]
    override def read(b: Array[Byte], off: Int, len: Int): Int = {
      @tailrec
      def readIter(acc: Int, b: Array[Byte], off: Int, len: Int): Int = {
        val currentPage: Int = (position / PAGE_SIZE).toInt
        val currentOffset: Int = (position % PAGE_SIZE).toInt

        val count: Int = Math.min(len, length - position).toInt

        if (count == 0 || position >= length) acc
        else {
          val currentLength = Math.min(PAGE_SIZE - currentOffset, count)
          Array.copy(streamBuffers(currentPage), currentOffset, b, off, currentLength)

          position += currentLength

          readIter(acc + currentLength, b, off + currentLength, len - currentLength)
        }
      }

      readIter(0, b, off, len)
    }

    /**
      * Skips over and discards <wyn>n</wyn> bytes of data from this input
      * stream. The <wyn>skip</wyn> method may, for a variety of reasons, end
      * up skipping over some smaller number of bytes, possibly <wyn>0</wyn>.
      * This may result from any of a number of conditions; reaching end of file
      * before <wyn>n</wyn> bytes have been skipped is only one possibility.
      * The actual number of bytes skipped is returned. If <wyn>n</wyn> is
      * negative, the <wyn>skip</wyn> method for class <wyn>InputStream</wyn> always
      * returns 0, and no bytes are skipped. Subclasses may handle the negative
      * value differently.
      *
      * The <wyn>skip</wyn> method of this class creates a
      * byte array and then repeatedly reads into it until <wyn>n</wyn> bytes
      * have been read or the end of the stream has been reached. Subclasses are
      * encouraged to provide a more efficient implementation of this method.
      * For instance, the implementation may depend on the ability to seek.
      *
      * @param      n the number of bytes to be skipped.
      * @return the actual number of bytes skipped.
      */

    @throws[IOException]
    override def skip(n: Long): Long = {
      if (n < 0) 0
      else {
        position = Math.min(position + n, length)
        length - position
      }
    }
  }
}

易于使用,无缓冲区重复,无2GB内存限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
val out: HugeMemoryOutputStream = new HugeMemoryOutputStream(initialCapacity /*may be 0*/)

out.write(...)
...

val in1: InputStream = out.asInputStream()

in1.read(...)
...

val in2: InputStream = out.asInputStream()

in2.read(...)
...

虽然不能将OuttoSt流转换为输入流,但Java提供了一种使用PiPooOuttuStudio和PiPoDePixSt流的方法,您可以将数据写入到PiPoDoPutsStudio中,以便通过相关的PiPixIdStudio获得可用的数据流。
以前,我在处理第三方库时遇到了类似的情况,这些库要求将一个inputstream实例传递给它们,而不是输出流实例。
我解决此问题的方法是使用pipedinputstream和pipedoutputstream。
顺便说一下,它们很难使用,您必须使用多线程来实现您想要的。我最近在Github上发布了一个可以使用的实现。
这里是链接。你可以通过wiki了解如何使用它。


旧的帖子,但可以帮助其他人,使用以下方式:

1
2
3
4
5
OutputStream out = new ByteArrayOutputStream();
...
out.write();
...
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(out.toString().getBytes()));


如果要从输入流生成输出流,则有一个基本问题。写入OutputStream块的方法,直到完成为止。因此,当写入方法完成时,结果是可用的。这有两个后果:

  • 如果只使用一个线程,则需要等到所有内容都写入(因此需要将流的数据存储在内存或磁盘中)。
  • 如果要在数据完成之前访问它,则需要另一个线程。
  • 变量1可以使用字节数组或字段实现。变种1可以使用pipies实现(直接或通过额外的抽象实现——例如,ringbuffer或来自其他注释的google lib)。

    事实上,用标准的Java,没有其他的方法来解决这个问题。每个解决方案都是其中一个的实现。

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

    • 有一个特殊的输出流需要一定数量的数据
    • 如果到达ammount,则流将控制它的对应流,这是一个特殊的输入流。
    • 输入流使数据量在被读取之前是可用的,此后,它将控制权传递回输出流。

    虽然有些语言有这个概念,但对于Java来说,你需要一些"魔法"。例如,Apache中的"Cason JavaFLUW"实现了Java。缺点是这需要在构建时修改一些特殊的字节码。所以把所有的东西放在一个额外的库中,用定制的构建脚本是有意义的。