2015-08-06 13 views
7

Ich sehe eine seltsame Situation mit kleinen Ausgabepuffern mit Java 8u45 und der java.util.Deflater.deflate(byte[] b, int off, int len, int flush) Methode, wenn mit kleinen Ausgabepuffern verwendet.Deflater.Deflate und kleine Ausgabepuffer

(Ich arbeite an einigen Code Low Level-Networking zu WebSocket anstehenden permessage-deflate Erweiterung im Zusammenhang, so kleine Puffer sind für mich eine Realität)

Der Beispielcode:

package deflate; 

import java.nio.charset.StandardCharsets; 
import java.util.zip.Deflater; 

public class DeflaterSmallBufferBug 
{ 
    public static void main(String[] args) 
    { 
     boolean nowrap = true; 
     Deflater deflater = new Deflater(Deflater.DEFAULT_COMPRESSION,nowrap); 

     byte[] input = "Hello".getBytes(StandardCharsets.UTF_8); 

     System.out.printf("input is %,d bytes - %s%n",input.length,getHex(input,0,input.length)); 

     deflater.setInput(input); 

     byte[] output = new byte[input.length]; 

     // break out of infinite loop seen with bug 
     int maxloops = 10; 

     // Compress the data 
     while (maxloops-- > 0) 
     { 
      int compressed = deflater.deflate(output,0,output.length,Deflater.SYNC_FLUSH); 
      System.out.printf("compressed %,d bytes - %s%n",compressed,getHex(output,0,compressed)); 

      if (compressed < output.length) 
      { 
       System.out.printf("Compress success"); 
       return; 
      } 
     } 

     System.out.printf("Exited compress (maxloops left %d)%n",maxloops); 
    } 

    private static String getHex(byte[] buf, int offset, int len) 
    { 
     StringBuilder hex = new StringBuilder(); 
     hex.append('['); 
     for (int i = offset; i < (offset + len); i++) 
     { 
      if (i > offset) 
      { 
       hex.append(' '); 
      } 
      hex.append(String.format("%02X",buf[i])); 
     } 
     hex.append(']'); 
     return hex.toString(); 
    } 
} 

Im obigen Fall , Ich versuche, komprimierte Bytes für die Eingabe "Hello" mit einem Ausgabepuffer von 5 Bytes Länge zu generieren.

würde ich das folgende resultierende Bytes annehmen:

buffer 1 [ F2 48 CD C9 C9 ] 
buffer 2 [ 07 00 00 00 FF ] 
buffer 3 [ FF ] 

Welche

[ F2 48 CD C9 C9 07 00 ] <-- the compressed data 
[ 00 00 FF FF ]   <-- the deflate tail bytes 

jedoch als

übersetzt, wenn Deflater.deflate() mit einem kleinen Puffer verwendet wird, ist das normal Schleife bei 5 Byte unendlich weiter komprimierten Daten (scheint nur bei Puffern von 5 Bytes oder niedriger zu manifestieren).

resultierender Ausgang die oben Demo läuft ...

input is 5 bytes - [48 65 6C 6C 6F] 
compressed 5 bytes - [F2 48 CD C9 C9] 
compressed 5 bytes - [07 00 00 00 FF] 
compressed 5 bytes - [FF 00 00 00 FF] 
compressed 5 bytes - [FF 00 00 00 FF] 
compressed 5 bytes - [FF 00 00 00 FF] 
compressed 5 bytes - [FF 00 00 00 FF] 
compressed 5 bytes - [FF 00 00 00 FF] 
compressed 5 bytes - [FF 00 00 00 FF] 
compressed 5 bytes - [FF 00 00 00 FF] 
compressed 5 bytes - [FF 00 00 00 FF] 
Exited compress (maxloops left -1) 

Wenn Sie die Eingabe/Ausgabe von mehr als 5 Bytes machen dann scheint das Problem weg zu gehen. (So ​​stellen Sie die Eingabezeichenfolge "Hellox" diese für sich selbst zu testen)

Ergebnisse des Puffers 6 Bytes machen (Eingang als "Hellox")

input is 6 bytes - [48 65 6C 6C 6F 78] 
compressed 6 bytes - [F2 48 CD C9 C9 AF] 
compressed 6 bytes - [00 00 00 00 FF FF] 
compressed 5 bytes - [00 00 00 FF FF] 
Compress success 

Auch diese Ergebnisse sind wenig schrullig mir, wie es scheint sind 2 deflate Tail-Byte-Sequenzen vorhanden.

Also meine ich meine ultimative Frage ist, vermisse ich etwas über die Deflater Verwendung, die für mich seltsam macht, oder zeigt dies auf einen möglichen Fehler in der JVM Deflater Implementierung selbst?

Update: 7. August 2015

Diese Entdeckung als bugs.java.com/JDK-8133170 angenommen wurde

Antwort

5

Dies ist ein zlib "Feature", in zlib.h dokumentiert:

Im Fall eines Z_FULL_FLUSH oder Z_SYNC_FLUSH, stellen Sie sicher, dass avail_out größer als sechs ist, um wiederholte Flush-Marker aufgrund von avail_out == 0 bei der Rückkehr zu vermeiden.

Was geschieht, ist, dass jeder Aufruf von deflate() mit Z_SYNC_FLUSH ist ein Fünf-Byte-Flush-Marker einsetzen. Da Sie nicht genügend Ausgabefläche zum Abrufen der Markierung bereitstellen, rufen Sie erneut auf, um mehr Ausgabe zu erhalten. Sie werden jedoch aufgefordert, gleichzeitig eine weitere Flushmarkierung einzufügen.

Was sollten Sie tun, ist deflate() mit Z_SYNC_FLUSHeinmal nennen, und dann alle verfügbaren Ausgabe mit zusätzlichen deflate() Anrufe bekommen, falls erforderlich, die Z_NO_FLUSH (oder NO_FLUSH in Java) verwenden.

+0

Dang! Mark Adler hat auf meine Frage geantwortet, es ist schwer, eine autoritativere Antwort als diese zu bekommen. :-) –

+1

Ich denke, ich sollte einen Apidoc-Fehler bei Oracle einreichen, um diesen Leckerbissen der Deflater-Dokumentation hinzuzufügen. Scheint nützlich zu wissen. –

+0

Es sollte als "Fehler" betrachtet werden, oder? Warum macht zlib das? Um eine Flagge zu speichern? – ZhongYu