【问题标题】:Fast way to compare inputstreams比较输入流的快速方法
【发布时间】:2011-05-13 20:25:09
【问题描述】:

我有一个问题,我需要快速比较两个输入流。

今天我有一个这样的功能:

private boolean isEqual(InputStream i1, InputStream i2) throws IOException {

    try {
        // do the compare
        while (true) {
            int fr = i1.read();
            int tr = i2.read();

            if (fr != tr)
                return false;

            if (fr == -1)
                return true;
        }

    } finally {
        if (i1 != null)
            i1.close();
        if (i2 != null)
            i2.close();
    }
}

但它真的很慢。我想使用缓冲读取,但没有想出一个好的方法。

一些额外的东西让它变得更难:

  • 我不想将其中一个输入流读入内存(整个)
  • 我不想使用第三方库

我需要一个实用的解决方案 - 代码! :)

【问题讨论】:

  • 我不认为你可以在不读入内存的情况下比较任何东西。你真的是说将整个输入流读入内存,也就是说读取固定数量的字节就可以了吗?
  • 我的意思是不能将整个输入流读入内存

标签: java compare inputstream


【解决方案1】:

使用缓冲读取只是用 BufferedInputStreams 包装 InputStreams 的问题。但是,一次读取大块可能会获得最佳性能。

private boolean isEqual(InputStream i1, InputStream i2) throws IOException {
    byte[] buf1 = new byte[64 *1024];
    byte[] buf2 = new byte[64 *1024];
    try {
        DataInputStream d2 = new DataInputStream(i2);
        int len;
        while ((len = i1.read(buf1)) > 0) {
            d2.readFully(buf2,0,len);
            for(int i=0;i<len;i++)
              if(buf1[i] != buf2[i]) return false;
        }
        return d2.read() < 0; // is the end of the second file also.
    } catch(EOFException ioe) {
        return false;
    } finally {
        i1.close();
        i2.close();
    }
}

【讨论】:

  • 那么,我该怎么做 - 例如一个实用的解决方案?
  • @dacwe:分配两个字节缓冲区byte[] buf1 = new byte[BlockSize]; byte[] buf2 = new byte[BlockSize];,并在从 i1 和 i2 读取这两个缓冲区后比较 buf1 和 buf2。
  • @patrick, Peter Lawrey:嗯,那可没那么容易.. :) sfussenegger 以为他有,但他也错了。
  • @dacwe,有什么不那么容易的?
  • 哇,我使用了您的解决方案,将 for 循环替换为 Arrays#equals。对于 200mb 的文件,速度是原来的两倍。我认为是因为该方法是 HotSpotIntrinsicCandidate。
【解决方案2】:

为什么不简单地在方法的最开始包装两个流:

i1 = new BufferedInputStream(i1);
i2 = new BufferedInputStream(i2);

或者,您可以简单地尝试将两个流读入缓冲区:

public static boolean equals(InputStream i1, InputStream i2, int buf) throws IOException {
    try {
        // do the compare
        while (true) {
            byte[] b1 = new byte[buf];
            byte[] b2 = new byte[buf];

            int length = i1.read(b1);
            if (length == -1) {
                return i2.read(b2, 0, 1) == -1;
            }

            try {
                StreamUtils.readFully(i2, b2, 0, length);
            } catch (EOFException e) {
                // i2 is shorter than i1
                return false;
            }

            if (!ArrayUtils.equals(b1, b2, 0, length)) {
                return false;
            }
        }
    } finally {
        // simply close streams and ignore (log) exceptions
        StreamUtils.close(i1, i2);
    }
}

// StreamUtils.readFully(..) 
public static void readFully(InputStream in, byte[] b, int off, int len) throws EOFException, IOException {
    while (len > 0) {
        int read = in.read(b, off, len);
        if (read == -1) {
            throw new EOFException();
        }
        off += read;
        len -= read;
    }
}

// ArrayUtils.equals(..)
public static boolean equals(byte[] a, byte[] a2, int off, int len) {
    if (off < 0 || len < 0 || len > a.length - off || len > a2.length - off) {
        throw new IndexOutOfBoundsException();
    } else if (len == 0) {
        return true;
    }

    if (a == a2) {
        return true;
    }
    if (a == null || a2 == null) {
        return false;
    }

    for (int i = off; i < off + len; i++) {
        if (a[i] != a2[i]) {
            return false;
        }
    }

    return true;
}

编辑:我现在已经修复了我的实现。这就是没有 DataInputStream 或 NIO 的样子。代码是available at GitHub 或来自Sonatype's OSS Snapshot Repository Maven:

<dependency>
  <groupId>at.molindo</groupId>
  <artifactId>molindo-utils</artifactId>
  <version>1.0-SNAPSHOT</version>
</dependency>

【讨论】:

  • read 没有为此指定方法(可能返回未读取完整输入!)
  • 另外,如果length=100 包含b1[1023],是否可以预测?
  • 我找不到“Arrays.equals(b1, b2, 0, length)”
  • @dacwe 我自己也注意到了这一点。这就是为什么我添加了 FIXME 评论 - 正在进行中;)@khachik 你所说的原子读取是什么意思? @peter Arrays.equals(..) 实际上是我正在使用的一个私有实用程序库,我的错,虽然它在 java.util.Arrays 中...要添加它
【解决方案3】:

这样的事情可能会做:

private static boolean isEqual(InputStream i1, InputStream i2)
        throws IOException {

    ReadableByteChannel ch1 = Channels.newChannel(i1);
    ReadableByteChannel ch2 = Channels.newChannel(i2);

    ByteBuffer buf1 = ByteBuffer.allocateDirect(1024);
    ByteBuffer buf2 = ByteBuffer.allocateDirect(1024);

    try {
        while (true) {

            int n1 = ch1.read(buf1);
            int n2 = ch2.read(buf2);

            if (n1 == -1 || n2 == -1) return n1 == n2;

            buf1.flip();
            buf2.flip();

            for (int i = 0; i < Math.min(n1, n2); i++)
                if (buf1.get() != buf2.get())
                    return false;

            buf1.compact();
            buf2.compact();
        }

    } finally {
        if (i1 != null) i1.close();
        if (i2 != null) i2.close();
    }
}

【讨论】:

  • @dacwe,我可以保证它比我提供的解决方案慢。 ;)
  • allocateDirect 应该为您提供一个“直接字节缓冲区”(具有本机低级实现),它应该比普通字节缓冲区更快。
  • @Peter Lawrey,你确定吗?您正在使用由 JVM 管理的字节数组,通过直接字节缓冲区,您离芯片更近了一步:-)
  • 它使 read() 更快,但是 get() 比访问 byte[] 作为 JNI 调用要慢。
  • 只有在下级 InputStreams 是 FileInputStreams 并且能够将数据直接从文件移动到 DirectByteBuffer 而无需进入 jvm 托管内存的情况下,使用 DirectByteBuffer 才会增加价值。可以说这是一个“正常”的 InputStream,它由文件以外的东西支持。在这种情况下,您要将内存从 jvm(源 InputStream)移到本机(DirectByteBuffer),然后再移回 jvm(在调用 buf.get() 时)以进行比较。
【解决方案4】:

到目前为止,我最喜欢的是使用来自 Apache Commons IO libraryorg.apache.commons.io.IOUtils 助手类:

IOUtils.contentEquals( is1, is2 );

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-22
    • 2014-08-20
    • 2014-07-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多