2017-05-31 28 views
8

Khi thử nghiệm với nén ZLib, tôi đã gặp phải một vấn đề lạ. Giải nén một mảng byte nén zlib với dữ liệu ngẫu nhiên không thể tái tạo nếu mảng nguồn dài ít nhất 32752 byte. Đây là một chương trình nhỏ tái tạo sự cố, bạn có thể see it in action on IDEOne. Các phương pháp nén và giải nén là mã tiêu chuẩn được chọn hướng dẫn.Giải nén ZLib không thành công trên mảng byte lớn

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

Đây có phải là lỗi đã biết trong ZLib không? Hoặc tôi có một lỗi trong thói quen nén/giải nén của tôi?

Trả lời

4

Đó là một lỗi trong logic của nén/giải nén các phương pháp; Tôi không phải là sâu này trong việc triển khai nhưng với gỡ lỗi tôi tìm thấy những điều sau đây:

Khi bộ đệm 32752 byte được nén, phương pháp deflater.deflate() trả về giá trị 32767, đây là kích thước mà bạn đã khởi tạo bộ đệm trong dòng:

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

Nếu bạn tăng kích thước bộ đệm ví dụ để

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

các bạn sẽ thấy, rằng đầu vào của 32.752 byte thực sự được xì hơi đến 32768 byte. Vì vậy, trong mã của bạn, dữ liệu nén không chứa tất cả dữ liệu cần có trong đó.

Khi bạn cố gắng giải nén, phương pháp inflater.inflate() trả về giá trị 0 cho biết cần thêm dữ liệu đầu vào. Nhưng khi bạn chỉ kiểm tra inflater.finished() bạn kết thúc bằng một vòng lặp vô tận. Vì vậy, bạn có thể tăng kích thước bộ đệm khi nén, nhưng điều đó có nghĩa là có vấn đề với các tệp lớn hơn, hoặc bạn cần viết lại để nén/giải nén logic để xử lý dữ liệu của bạn theo khối.

+0

Cảm ơn bạn. Như được viết, nó không phải là mã của tôi, và bây giờ tôi đã thay thế nó bằng mã làm việc. Nhưng cảm ơn vì đã khai sáng cho tôi về những gì sai với mã. –

+0

là một câu hỏi hay; Tôi thích các lỗi săn mồi như thế này ;-) –

+0

Điều tra rất hay! – nobeh

4

Dường như phương thức compress() bị lỗi. Cái này hoạt động:

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

bạn cũng cần phải kiểm tra inflater.inflate() để trả lại 0 –

Các vấn đề liên quan