6

有人能告诉我如何克隆输入流,尽可能少地花费创建时间吗?我需要多次克隆输入流以使用多种方法来处理 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。这很快(最慢的创建时间 == 1 毫秒。最快的 == 0 毫秒)。但是,在我发送 is1 进行处理后,处理 is2 和 is3 的方法抛出了一个错误,说没有要处理的内容,几乎就像下面所有 3 个变量都引用了同一个 IS。

    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) 输入流的大小未知

4

4 回答 4

6

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

您可以创建某种自定义ReusableInputStream类,其中您还可以在第一次完整读取时立即写入内部 ,然后在读取最后一个字节时将ByteArrayOutputStream其包装在 a中,最后在随后自动翻转的完整读取中重用相同的内容当达到极限时。这使您免于像第一次尝试那样进行一次完整阅读。ByteBufferByteBuffer

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

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.
}

(请注意,实际作业是在 inint read(byte[], int, int)而不是 in 中执行的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 秒内完成。

于 2012-11-09T15:56:50.220 回答
3

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

复制流需要时间,并且(通常)这是克隆流的唯一方法。除非您收紧问题的范围,否则性能可以显着提高的可能性很小。

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

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

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

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

于 2012-11-09T03:06:06.623 回答
2

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

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

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

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

于 2012-11-09T06:49:18.267 回答
1

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

我现在不在计算机附近进行测试,但是我认为您最好以块的形式读取输入,例如 1024 字节,然后将这些块(或块的数组副本)推送到您的带有附加到其线程末端的输入流的输出流。如果没有可用数据等,请让您的读者屏蔽。

于 2012-11-09T06:33:06.220 回答