如何在最短的时间内在java中克隆输入流

有人能告诉我如何克隆输入流,尽可能少的创建时间? 我需要多次克隆输入流以便多种方法来处理IS。 我尝试了三种方法,因为某种原因,事情不起作用。

方法#1:感谢stackoverflow社区,我发现以下链接很有帮助,并将代码片段合并到我的程序中。

如何克隆InputStream?

但是,使用此代码最多可能需要一分钟(对于10MB文件)来创建克隆的输入流,并且我的程序需要尽可能快。

int read = 0; byte[] bytes = new byte[1024*1024*2]; ByteArrayOutputStream bos = new ByteArrayOutputStream(); while ((read = is.read(bytes)) != -1) bos.write(bytes,0,read); byte[] ba = bos.toByteArray(); InputStream is1 = new ByteArrayInputStream(ba); InputStream is2 = new ByteArrayInputStream(ba); InputStream is3 = new ByteArrayInputStream(ba); 

方法#2:我也尝试使用BufferedInputStream来克隆IS。 这很快(创建时间最慢== 1ms。最快== 0ms)。 但是,在我发送is1进行处理之后,方法处理is2和is3引发了一个错误,说没有什么可以处理,几乎就像下面引用相同的IS的所有3个变量一样。

  is = getFileFromBucket(path,filename); ... ... InputStream is1 = new BufferedInputStream(is); InputStream is2 = new BufferedInputStream(is); InputStream is3 = new BufferedInputStream(is); 

方法#3:我认为编译器对我说谎。 我为上面两个例子检查了is1的markSupported()。 它返回true,所以我以为我可以跑

  is1.mark() is1.reset() 

要不就

  is1.reset(); 

在将IS传递给我各自的方法之前。 在上面的两个例子中,我收到一条错误,说它是无效标记。

我现在没有想法,所以提前感谢你能给我的任何帮助。

PS从我收到的人们的评论中,我需要澄清一些与我的情况有关的事情:1)这个程序在VM上运行2)输入流从另一个方法传递给我。 我不是从本地文件中读取3)输入流的大小未知

如何克隆输入流,尽可能少的创建时间? 我需要多次克隆输入流以便多种方法来处理IS

您可以创建某种自定义的ReusableInputStream类,其中您在第一次完整读取时立即写入内部ByteArrayOutputStream ,然后在读取最后一个字节时将其包装在ByteBuffer ,最后在后续完整读取时重用相同的ByteBuffer当达到限制时会自动翻转。 这样可以避免您在第一次尝试时完全阅读。

这是一个基本的启动示例:

 public class ReusableInputStream extends InputStream { private InputStream input; private ByteArrayOutputStream output; private ByteBuffer buffer; public ReusableInputStream(InputStream input) throws IOException { this.input = input; this.output = new ByteArrayOutputStream(input.available()); // Note: it's resizable anyway. } @Override public int read() throws IOException { byte[] b = new byte[1]; read(b, 0, 1); return b[0]; } @Override public int read(byte[] bytes) throws IOException { return read(bytes, 0, bytes.length); } @Override public int read(byte[] bytes, int offset, int length) throws IOException { if (buffer == null) { int read = input.read(bytes, offset, length); if (read <= 0) { input.close(); input = null; buffer = ByteBuffer.wrap(output.toByteArray()); output = null; return -1; } else { output.write(bytes, offset, read); return read; } } else { int read = Math.min(length, buffer.remaining()); if (read <= 0) { buffer.flip(); return -1; } else { buffer.get(bytes, offset, read); return read; } } } // You might want to @Override flush(), close(), etc to delegate to input. } 

(请注意,实际作业是在int read(byte[], int, int)而不是在int read() ,因此当调用者本身也使用byte[]缓冲区进行流式传输时,预期会更快。

您可以按如下方式使用它:

 InputStream input = new ReusableInputStream(getFileFromBucket(path,filename)); IOUtils.copy(input, new FileOutputStream("/copy1.ext")); IOUtils.copy(input, new FileOutputStream("/copy2.ext")); IOUtils.copy(input, new FileOutputStream("/copy3.ext")); 

至于性能,每10MB 1分钟更可能是硬件问题,而不是软件问题。 我的7200rpm笔记本电脑硬盘在不到1秒的时间内完成。

但是,使用此代码最多可能需要一分钟(对于10MB文件)来创建克隆的输入流,并且我的程序需要尽可能快。

复制流需要时间,并且(通常)这是克隆流的唯一方法。 除非你收紧问题的范围,否则几乎不可能显着改善性能。

以下是可以改进的几种情况:

  • 如果您事先知道流中的字节数,那么您可以直接读入最终的字节数组。

  • 如果您知道数据来自文件,则可以为该文件创建内存映射缓冲区。

但根本问题是移动大量字节需要花费时间。 事实上,10Mb文件花了1分钟(使用你的问题中的代码) 表明真正的瓶颈根本不在Java中。

关于第一种方法,包括将所有字节放在ByteArrayOutputStream中:

  • 首先,这种方法消耗大量内存。 如果您没有确保JVM启动时分配了足够的内存,则需要在处理流时动态请求内存,这非常耗时。
  • 您最初使用32字节的缓冲区创建ByteArrayOutputStream。 每次尝试在其中放入某些内容时,如果它不适合现有的字节数组,则会创建一个新的更大的数组,并将旧的字节复制到新的数组中。 由于每次使用2MB输入,因此强制ByteArrayOutputStream将其数据一遍又一遍地复制到更大的arrays中,每次增加其数组的大小为2MB。
  • 由于旧的数组是垃圾,垃圾收集器可能会回收它们的内存,这使得复制过程更加缓慢。
  • 也许您应该使用指定初始缓冲区大小的构造函数来定义ByArrayOutputStream。 设置大小越准确,过程应该越快,因为需要更少的中间副本。

第二种方法是假的,你不能在不同的其他流中装饰相同的输入流并期望这些东西能够工作。 由于字节被一个流消耗,内部流也被耗尽,并且不能为其他流提供准确的数据。

在我扩展我的答案之前,让我问一下,您的其他方法是否期望接收在单独线程上运行的输入流的副本? 因为如果是这样,这听起来像是PipedOutputStream和PipedInputStream的工作?

您是否打算将单独的方法并行或顺序运行? 如果顺序,我认为没有理由克隆输入流,所以我不得不假设您计划分离线程来管理每个流。

我现在不是在电脑附近测试这个,但我认为你最好以块为单位读取输入,比如1024字节,然后将这些块(或块的数组副本)推送到你的输出流,其输入流附加到其线程末端。 如果没有可用的数据,请让您的读者阻止等。