2010-11-22 22 views
20

Mam problem, muszę szybko porównać dwa strumienie wejściowe.Szybki sposób porównywania danych wejściowych

Dziś mam funkcję tak:

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(); 
    } 
} 

Ale to jest bardzo powolny. Chcę używać buforowanych odczytów, ale nie wymyśliłem dobrego sposobu na zrobienie tego.

Niektóre rzeczy, które dodatkowo utrudnia:

  • Nie chcę czytać jedną z strumieni wejściowych do pamięci (cały jeden)
  • Nie chcę używać osoby trzeciej biblioteka

Potrzebuję praktycznego rozwiązania - kod! :)

+0

I nie myśl, że możesz porównać coś bez czytania go do pamięci. Czy naprawdę chodzi o przeczytanie * całego wejścia * do pamięci, co oznacza, że ​​czytanie stałej liczby bajtów jest w porządku? – Patrick

+0

Mam na myśli odczytywanie całego strumienia wejściowego do pamięci nie jest opcją – dacwe

Odpowiedz

15

Coś takiego może zrobić:

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(); 
    } 
} 
+0

+1 Podoba mi się. NIO ftw :) – Patrick

+0

Uderzenie w cel! – dacwe

+0

@dacwe, mogę zagwarantować, że jest wolniejszy niż rozwiązanie, które podałem. ;) –

8

Używanie zbuforowanych odczytów to tylko kwestia zawijania obiektów InputStream za pomocą BufferedInputStreams. Jednak prawdopodobnie uzyskasz najlepszą wydajność podczas czytania dużych bloków na raz.

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(); 
    } 
} 
+0

Więc, jak to zrobić - np. praktyczne rozwiązanie? – dacwe

+0

@dacwe: Przydzielanie bajtów dwubajtowych 'byte [] buf1 = nowy bajt [BlockSize]; byte [] buf2 = nowy bajt [BlockSize]; 'i porównaj buf1 i buf2 po wczytaniu tych dwóch buforów z i1 i i2. – Patrick

+0

@patrick, Peter Lawrey: Cóż, to nie takie proste ... :) sfussenegger myślał, że on to ma, ale też się myli. – dacwe

2

dlaczego nie po prostu owinąć obydwa strumienie na samym początku swojej metody:

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

Alternatywnie, można po prostu spróbować przeczytaniu obu strumieni do bufora:

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

EDIT: Naprawiłem teraz moją implementację. Tak to wygląda bez DataInputStream lub NIO. Kod jest available at GitHub lub Sonatype's OSS Snapshot Repository Maven:

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

Zasadniczo to nie zadziała z powodu porównywania odczytów atomowych ... – khachik

+1

Metoda 'read' nie została do tego określona (mogłaby powrócić nie czytając pełnego wejścia!) – dacwe

+0

Czy jest przewidywalne co zawiera powiedz" b1 [1023] ", jeśli' length = 100'? – khachik

Powiązane problemy