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

时间:2012-11-09 02:04:02

标签: java clone inputstream bufferedinputstream

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

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

How to clone an 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传递给我各自的方法之前。在上面的两个例子中,我收到一个错误,说它是无效标记。

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

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

4 个答案:

答案 0 :(得分:6)

  

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

您可以创建某种自定义ReusableInputStream类,其中立即也会在第一次完整读取时写入内部ByteArrayOutputStream,然后将其包装在{ {3}}读取最后一个字节,最后在后续完整读取时重用相同的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秒的时间内完成。

答案 1 :(得分:3)

  

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

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

以下是一些可以改进的情况:

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

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

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

答案 2 :(得分:2)

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

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

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

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

答案 3 :(得分:1)

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

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