【问题标题】:How to clone an InputStream?如何克隆 InputStream?
【发布时间】:2011-08-20 21:31:15
【问题描述】:

我有一个 InputStream,我将它传递给一个方法来进行一些处理。我将在其他方法中使用相同的 InputStream,但在第一次处理后,InputStream 似乎在方法内部关闭。

如何克隆 InputStream 以发送到关闭他的方法?还有其他解决方案吗?

编辑:关闭 InputStream 的方法是来自库的外部方法。我无法控制是否关闭。

private String getContent(HttpURLConnection con) {
    InputStream content = null;
    String charset = "";
    try {
        content = con.getInputStream();
        CloseShieldInputStream csContent = new CloseShieldInputStream(content);
        charset = getCharset(csContent);            
        return  IOUtils.toString(content,charset);
    } catch (Exception e) {
        System.out.println("Error downloading page: " + e);
        return null;
    }
}

private String getCharset(InputStream content) {
    try {
        Source parser = new Source(content);
        return parser.getEncoding();
    } catch (Exception e) {
        System.out.println("Error determining charset: " + e);
        return "UTF-8";
    }
}

【问题讨论】:

  • 您想在方法返回后“重置”流吗?即,从头开始读取流?
  • 是的,关闭 InputStream 的方法返回它被编码的字符集。第二种方法是使用第一种方法中找到的字符集将 InputStream 转换为字符串。
  • 在这种情况下,您应该能够做我在回答中描述的事情。
  • 我不知道解决它的最佳方法,但我解决了我的问题。 Jericho HTML Parser 的 toString 方法返回以正确格式格式化的字符串。这就是我现在所需要的。

标签: java clone inputstream


【解决方案1】:

如果您只想多次读取相同的信息,并且输入数据小到足以放入内存,您可以将数据从您的InputStream 复制到ByteArrayOutputStream

然后,您可以获得相关的字节数组,并打开任意数量的“克隆”ByteArrayInputStreams。

ByteArrayOutputStream baos = new ByteArrayOutputStream();

// Code simulating the copy
// You could alternatively use NIO
// And please, unlike me, do something about the Exceptions :D
byte[] buffer = new byte[1024];
int len;
while ((len = input.read(buffer)) > -1 ) {
    baos.write(buffer, 0, len);
}
baos.flush();
    
// Open new InputStreams using recorded bytes
// Can be repeated as many times as you wish
InputStream is1 = new ByteArrayInputStream(baos.toByteArray()); 
InputStream is2 = new ByteArrayInputStream(baos.toByteArray()); 

但如果您确实需要保持原始流打开以接收新数据,那么您将需要跟踪对close() 的外部调用。您需要防止以某种方式调用close()

更新(2019 年):

从 Java 9 开始,中间位可以替换为 InputStream.transferTo:

ByteArrayOutputStream baos = new ByteArrayOutputStream();
input.transferTo(baos);
InputStream firstClone = new ByteArrayInputStream(baos.toByteArray()); 
InputStream secondClone = new ByteArrayInputStream(baos.toByteArray()); 

【讨论】:

  • 我找到了另一个解决方案来解决我的问题,但不涉及复制 InputStream,但我认为如果我需要复制 InputStream,这是最好的解决方案。
  • 这种方法消耗的内存与输入流的全部内容成正比。最好使用TeeInputStream,如here 的答案中所述。
  • IOUtils(来自 apache commons)有一个复制方法,可以在代码中间进行缓冲区读/写。
  • @aioobe 很抱歉询问这么老的评论,但您是否测试过内存消耗,如果是,您的流是否或多或少同时消耗?建议的解决方案(链接在您的评论中)使用BufferedReader,即backed by an array,与上述答案中使用的ByteArrayOutputStream 相同。
  • 我没有看到使用TeeInputStream 会如何改善内存消耗,除了上面的答案使用第二次调用baos.toByteArray() 而不是使用PipedInputStream 进行第二次复制
【解决方案2】:

你想使用Apache的CloseShieldInputStream

这是一个包装器,可以防止流被关闭。你会做这样的事情。

InputStream is = null;

is = getStream(); //obtain the stream 
CloseShieldInputStream csis = new CloseShieldInputStream(is);

// call the bad function that does things it shouldn't
badFunction(csis);

// happiness follows: do something with the original input stream
is.read();

【讨论】:

  • 看起来不错,但在这里不起作用。我将使用代码编辑我的帖子。
  • CloseShield 不起作用,因为您原来的 HttpURLConnection 输入流正在某个地方关闭。您的方法不应该使用受保护的流IOUtils.toString(csContent,charset) 调用 IOUtils 吗?
  • 也许可以这样。我可以防止 HttpURLConnection 被关闭吗?
  • @Renato。也许问题根本不是 close() 调用,而是 Stream 正在被读取到最后的事实。由于mark()reset() 可能不是http 连接的最佳方法,也许你应该看看我的回答中描述的字节数组方法。
  • 还有一点,您可以随时打开到同一个 URL 的新连接。见这里:stackoverflow.com/questions/5807340/…
【解决方案3】:

你不能克隆它,你将如何解决你的问题取决于数据的来源是什么。

一种解决方案是将 InputStream 中的所有数据读取到一个字节数组中,然后围绕该字节数组创建一个 ByteArrayInputStream,并将该输入流传递给您的方法。

编辑 1: 也就是说,如果其他方法也需要读取相同的数据。即你想“重置”流。

【讨论】:

  • 我不知道您需要帮助的部分。我想你知道如何从流中读取?从 InputStream 中读取所有数据,并将数据写入 ByteArrayOutputStream。完成读取所有数据后,在 ByteArrayOutputStream 上调用 toByteArray()。然后将该字节数组传递给 ByteArrayInputStream 的构造函数。
【解决方案4】:

如果从流中读取的数据很大,我建议使用来自 Apache Commons IO 的 TeeInputStream。这样,您基本上可以复制输入并将 t'd 管道作为克隆传递。

【讨论】:

    【解决方案5】:

    这可能不适用于所有情况,但这是我所做的:我扩展了 FilterInputStream 类,并在外部库读取数据时对字节进行必要的处理。

    public class StreamBytesWithExtraProcessingInputStream extends FilterInputStream {
    
        protected StreamBytesWithExtraProcessingInputStream(InputStream in) {
            super(in);
        }
    
        @Override
        public int read() throws IOException {
            int readByte = super.read();
            processByte(readByte);
            return readByte;
        }
    
        @Override
        public int read(byte[] buffer, int offset, int count) throws IOException {
            int readBytes = super.read(buffer, offset, count);
            processBytes(buffer, offset, readBytes);
            return readBytes;
        }
    
        private void processBytes(byte[] buffer, int offset, int readBytes) {
           for (int i = 0; i < readBytes; i++) {
               processByte(buffer[i + offset]);
           }
        }
    
        private void processByte(int readByte) {
           // TODO do processing here
        }
    
    }
    

    然后,您只需传递一个 StreamBytesWithExtraProcessingInputStream 的实例,您将在输入流中传递。以原始输入流作为构造函数参数。

    需要注意的是,这是逐字节工作的,所以如果需要高性能,请不要使用它。

    【讨论】:

      【解决方案6】:

      UPD。 检查之前的评论。这不是所要求的。

      如果您使用apache.commons,您可以使用IOUtils 复制流。

      您可以使用以下代码:

      InputStream = IOUtils.toBufferedInputStream(toCopy);
      

      以下是适合您情况的完整示例:

      public void cloneStream() throws IOException{
          InputStream toCopy=IOUtils.toInputStream("aaa");
          InputStream dest= null;
          dest=IOUtils.toBufferedInputStream(toCopy);
          toCopy.close();
          String result = new String(IOUtils.toByteArray(dest));
          System.out.println(result);
      }
      

      这段代码需要一些依赖:

      MAVEN

      <dependency>
          <groupId>commons-io</groupId>
          <artifactId>commons-io</artifactId>
          <version>2.4</version>
      </dependency>
      

      GRADLE

      'commons-io:commons-io:2.4'
      

      这是此方法的 DOC 参考:

      获取 InputStream 的全部内容并表示相同的数据 结果输入流。这种方法在哪里很有用,

      源 InputStream 很慢。它有关联的网络资源,所以我们 不能长时间打开。它具有关联的网络超时。

      您可以在此处找到有关IOUtils 的更多信息: http://commons.apache.org/proper/commons-io/javadocs/api-2.4/org/apache/commons/io/IOUtils.html#toBufferedInputStream(java.io.InputStream)

      【讨论】:

      • 这不会克隆输入流,而只是缓冲它。那不一样; OP 想要重新读取(复制)同一个流。
      【解决方案7】:

      下面是 Kotlin 的解决方案。

      您可以将 InputStream 复制到 ByteArray

      val inputStream = ...
      
      val byteOutputStream = ByteArrayOutputStream()
      inputStream.use { input ->
          byteOutputStream.use { output ->
              input.copyTo(output)
          }
      }
      
      val byteInputStream = ByteArrayInputStream(byteOutputStream.toByteArray())
      

      如果您需要多次阅读byteInputStream,请在再次阅读之前致电byteInputStream.reset()

      https://code.luasoftware.com/tutorials/kotlin/how-to-clone-inputstream/

      【讨论】:

        【解决方案8】:

        下面的课程应该可以解决问题。只需创建一个实例,调用“multiply”方法,并提供源输入流和您需要的副本数量。

        重要提示:您必须在单独的线程中同时使用所有克隆的流。

        package foo.bar;
        
        import java.io.IOException;
        import java.io.InputStream;
        import java.io.PipedInputStream;
        import java.io.PipedOutputStream;
        import java.util.concurrent.ExecutorService;
        import java.util.concurrent.Executors;
        
        public class InputStreamMultiplier {
            protected static final int BUFFER_SIZE = 1024;
            private ExecutorService executorService = Executors.newCachedThreadPool();
        
            public InputStream[] multiply(final InputStream source, int count) throws IOException {
                PipedInputStream[] ins = new PipedInputStream[count];
                final PipedOutputStream[] outs = new PipedOutputStream[count];
        
                for (int i = 0; i < count; i++)
                {
                    ins[i] = new PipedInputStream();
                    outs[i] = new PipedOutputStream(ins[i]);
                }
        
                executorService.execute(new Runnable() {
                    public void run() {
                        try {
                            copy(source, outs);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
        
                return ins;
            }
        
            protected void copy(final InputStream source, final PipedOutputStream[] outs) throws IOException {
                byte[] buffer = new byte[BUFFER_SIZE];
                int n = 0;
                try {
                    while (-1 != (n = source.read(buffer))) {
                        //write each chunk to all output streams
                        for (PipedOutputStream out : outs) {
                            out.write(buffer, 0, n);
                        }
                    }
                } finally {
                    //close all output streams
                    for (PipedOutputStream out : outs) {
                        try {
                            out.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        

        【讨论】:

        • 不回答问题。他想在一种方法中使用流来确定字符集,然后然后在第二种方法中重新读取它及其字符集。
        【解决方案9】:

        克隆输入流可能不是一个好主意,因为这需要深入了解被克隆的输入流的详细信息。解决此问题的方法是创建一个新的输入流,该流再次从同一源读取。

        因此,使用一些 Java 8 功能,这将如下所示:

        public class Foo {
        
            private Supplier<InputStream> inputStreamSupplier;
        
            public void bar() {
                procesDataThisWay(inputStreamSupplier.get());
                procesDataTheOtherWay(inputStreamSupplier.get());
            }
        
            private void procesDataThisWay(InputStream) {
                // ...
            }
        
            private void procesDataTheOtherWay(InputStream) {
                // ...
            }
        }
        

        这个方法有一个积极的效果,它可以重用已经存在的代码——创建封装在inputStreamSupplier 中的输入流。并且无需为流的克隆维护第二条代码路径。

        另一方面,如果从流中读取成本很高(因为它是通过低带宽连接完成的),那么这种方法将使成本加倍。这可以通过使用特定的供应商来规避,该供应商将首先在本地存储流内容并为现在的本地资源提供InputStream

        【讨论】:

        • 这个答案我不清楚。如何从现有的 is 初始化供应商?
        • @user1156544 正如我所写克隆输入流可能不是一个好主意,因为这需要深入了解被克隆的输入流的详细信息。你不能使用供应商从现有的输入流中创建输入流。例如,供应商可以使用java.io.Filejava.net.URL 在每次调用时创建一个新的输入流。
        • 我现在明白了。这不适用于 OP 明确要求的 inputstream,但如果它们是原始数据源,则适用于 File 或 URL。谢谢
        【解决方案10】:

        通过示例增强@Anthony Accioly

        InputStream:克隆bytes-Stream 并提供副本数作为列表集合。

        public static List<InputStream> multiplyBytes(InputStream input, int cloneCount) throws IOException {
            List<InputStream> copies = new ArrayList<InputStream>();
            
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            copy(input, baos);
            
            for (int i = 0; i < cloneCount; i++) {
                copies.add(new ByteArrayInputStream(baos.toByteArray()));
            }
            return copies;
        }
        // IOException - If reading the Reader or Writing into the Writer goes wrong.
        public static void copy(Reader in, Writer out) throws IOException {
            try {
                char[] buffer = new char[1024];
                int nrOfBytes = -1;
                while ((nrOfBytes = in.read(buffer)) != -1) {
                    out.write(buffer, 0, nrOfBytes);
                }
                out.flush();
            } finally {
                close(in);
                close(out);
            }
        }
        

        Reader:克隆chars-Stream 并提供副本数作为列表集合。

        public static List<Reader> multiplyChars(Reader reader, int cloneCOunt) throws IOException {
            List<Reader> copies = new ArrayList<Reader>();
            BufferedReader bufferedInput = new BufferedReader(reader);
            StringBuffer buffer = new StringBuffer();
            String delimiter = System.getProperty("line.separator");
            String line;
            while ((line = bufferedInput.readLine()) != null) {
                if (!buffer.toString().equals(""))
                    buffer.append(delimiter);
                buffer.append(line);
            }
            close(bufferedInput);
            for (int i = 0; i < cloneCOunt; i++) {
                copies.add(new StringReader(buffer.toString()));
            }
            return copies;
        }
        public static void copy(InputStream in, OutputStream out) throws IOException {
            try {
                byte[] buffer = new byte[1024];
                int nrOfBytes = -1;
                while ((nrOfBytes = in.read(buffer)) != -1) {
                    out.write(buffer, 0, nrOfBytes);
                }
                out.flush();
            } finally {
                close(in);
                close(out);
            }
        }
        

        完整示例:

        public class SampleTest {
        
            public static void main(String[] args) throws IOException {
                String filePath = "C:/Yash/StackoverflowSSL.cer";
                InputStream fileStream = new FileInputStream(new File(filePath) );
                
                List<InputStream> bytesCopy = multiplyBytes(fileStream, 3);
                for (Iterator<InputStream> iterator = bytesCopy.iterator(); iterator.hasNext();) {
                    InputStream inputStream = (InputStream) iterator.next();
                    System.out.println("Byte Stream:"+ inputStream.available()); // Byte Stream:1784
                }
                printInputStream(bytesCopy.get(0));
                
                //java.sql.Clob clob = ((Clob) getValue(sql)); - clob.getCharacterStream();
                Reader stringReader = new StringReader("StringReader that reads Characters from the specified string.");
                List<Reader> charsCopy = multiplyChars(stringReader, 3);
                for (Iterator<Reader> iterator = charsCopy.iterator(); iterator.hasNext();) {
                    Reader reader = (Reader) iterator.next();
                    System.out.println("Chars Stream:"+reader.read()); // Chars Stream:83
                }
                printReader(charsCopy.get(0));
            }
            
            // Reader, InputStream - Prints the contents of the reader to System.out.
            public static void printReader(Reader reader) throws IOException {
                BufferedReader br = new BufferedReader(reader);
                String s;
                while ((s = br.readLine()) != null) {
                    System.out.println(s);
                }
            }
            public static void printInputStream(InputStream inputStream) throws IOException {
                printReader(new InputStreamReader(inputStream));
            }
        
            // Closes an opened resource, catching any exceptions.
            public static void close(Closeable resource) {
                if (resource != null) {
                    try {
                        resource.close();
                    } catch (IOException e) {
                        System.err.println(e);
                    }
                }
            }
        }
        
        

        【讨论】:

          猜你喜欢
          • 2016-07-16
          • 2010-10-17
          • 2012-05-27
          • 2012-06-17
          • 2010-12-30
          • 2015-01-26
          • 2011-03-31
          相关资源
          最近更新 更多