2017-05-31 16 views
8

ZLib圧縮を試してみると、私は奇妙な問題を抱えています。ソース配列の長さが32752バイト以上であれば、ランダムなデータを含むzlib圧縮バイト配列の解凍が再現不能になります。ここで問題を再現する小さなプログラムがあります。see it in action on IDEOneです。圧縮と解凍の方法はチュートリアルで選択された標準コードです。大きなバイト配列でZLibの解凍が失敗する

public class ZlibMain { 

    private static byte[] compress(final byte[] data) { 
     final Deflater deflater = new Deflater(); 
     deflater.setInput(data); 

     deflater.finish(); 
     final byte[] bytesCompressed = new byte[Short.MAX_VALUE]; 
     final int numberOfBytesAfterCompression = deflater.deflate(bytesCompressed); 
     final byte[] returnValues = new byte[numberOfBytesAfterCompression]; 
     System.arraycopy(bytesCompressed, 0, returnValues, 0, numberOfBytesAfterCompression); 
     return returnValues; 

    } 

    private static byte[] decompress(final byte[] data) { 
     final Inflater inflater = new Inflater(); 
     inflater.setInput(data); 
     try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length)) { 
      final byte[] buffer = new byte[Math.max(1024, data.length/10)]; 
      while (!inflater.finished()) { 
       final int count = inflater.inflate(buffer); 
       outputStream.write(buffer, 0, count); 
      } 
      outputStream.close(); 
      final byte[] output = outputStream.toByteArray(); 
      return output; 
     } catch (DataFormatException | IOException e) { 
      throw new RuntimeException(e); 
     } 
    } 

    public static void main(final String[] args) { 
     roundTrip(100); 
     roundTrip(1000); 
     roundTrip(10000); 
     roundTrip(20000); 
     roundTrip(30000); 
     roundTrip(32000); 
     for (int i = 32700; i < 33000; i++) { 
      if(!roundTrip(i))break; 
     } 
    } 

    private static boolean roundTrip(final int i) { 
     System.out.printf("Starting round trip with size %d: ", i); 
     final byte[] data = new byte[i]; 
     for (int j = 0; j < data.length; j++) { 
      data[j]= (byte) j; 
     } 
     shuffleArray(data); 

     final byte[] compressed = compress(data); 
     try { 
      final byte[] decompressed = CompletableFuture.supplyAsync(() -> decompress(compressed)) 
                 .get(2, TimeUnit.SECONDS); 
      System.out.printf("Success (%s)%n", Arrays.equals(data, decompressed) ? "matching" : "non-matching"); 
      return true; 
     } catch (InterruptedException | ExecutionException | TimeoutException e) { 
      System.out.println("Failure!"); 
      return false; 
     } 
    } 

    // Implementing Fisher–Yates shuffle 
    // source: https://stackoverflow.com/a/1520212/342852 
    static void shuffleArray(byte[] ar) { 
     Random rnd = ThreadLocalRandom.current(); 
     for (int i = ar.length - 1; i > 0; i--) { 
      int index = rnd.nextInt(i + 1); 
      // Simple swap 
      byte a = ar[index]; 
      ar[index] = ar[i]; 
      ar[i] = a; 
     } 
    } 
} 

これはZLibの既知のバグですか?または、私の圧縮/解凍ルーチンにエラーがありますか?

答えて

4

それは/メソッドを解凍圧縮のロジックのエラーです。私はこの深く実装されていませんが、デバッグで私は次のように見つけました:

deflater.deflate()メソッドは、327567バイトのバッファが圧縮されると、32767の値を返します。これは、バッファを初期化したサイズです。ライン:

final byte[] bytesCompressed = new byte[Short.MAX_VALUE]; 

あなたは

final byte[] bytesCompressed = new byte[4 * Short.MAX_VALUE]; 

に例えばバッファサイズを増やす場合は、32752バイトの入力が実際に32768バイトに収縮されていること、わかります。あなたのコードでは、圧縮されたデータにそこにあるべきすべてのデータが含まれているわけではありません。

解凍しようとすると、inflater.inflate()メソッドは、より多くの入力データが必要であることを示すゼロを返します。しかし、inflater.finished()をチェックするだけで、あなたは無限ループで終わります。

圧縮時にバッファサイズを増やすこともできますが、それは単に大きなファイルで問題が発生していることを意味します。データを圧縮/解凍してデータを処理する必要があります。

+0

ありがとうございました。書かれているように、それは私のコードではなく、私は今それを作業コードに置き換えました。しかし、コードの問題点について私に教えてくれてありがとう。 –

+0

はすてきな質問でした。私はこれのような狩猟のバグが大好きです;-) –

+0

非常に良い調査! – nobeh

4

明らかにcompress()メソッドに問題がありました。 この1つは動作します:

public static byte[] compress(final byte[] data) { 
    try (final ByteArrayOutputStream outputStream = 
            new ByteArrayOutputStream(data.length);) { 

     final Deflater deflater = new Deflater(); 
     deflater.setInput(data); 
     deflater.finish(); 
     final byte[] buffer = new byte[1024]; 
     while (!deflater.finished()) { 
      final int count = deflater.deflate(buffer); 
      outputStream.write(buffer, 0, count); 
     } 

     final byte[] output = outputStream.toByteArray(); 
     return output; 
    } catch (IOException e) { 
     throw new IllegalStateException(e); 
    } 
} 
+2

返すにはinflater.inflate()もチェックする必要があります –

関連する問題