2010-09-03 30 views
15

Nous travaillons sur un programme où nous devons vider (compresser et envoyer des données) un GZIPOutputStream. Le problème est que la méthode de vidage du GZIPOutputStream ne fonctionne pas comme prévu (force compresser et envoyer des données), mais le Stream attend plus de données pour une compression efficace des données. Quand vous appelez finish, les données sont compressées et envoyées sur le flux de sortie mais le GZIPOutputStream (pas le flux sous-jacent) sera fermé, donc nous ne pouvons pas écrire plus de données jusqu'à créer un nouveau GZIPOutputStream, ce qui coûte du temps et de la performance.Forcer le vidage sur un GZIPOutputStream en Java

J'espère que n'importe qui peut aider avec ceci.

Cordialement.

+0

Je travaille avec Java6. – Hemeroc

Répondre

8

Je ne l'ai pas encore essayé, et ce conseil ne sera pas utile jusqu'à ce que nous avons Java 7 à la main, mais la documentation pour la méthode de flush()GZIPOutputStream héritée de DeflaterOutputStream repose sur le mode de rinçage spécifié à la construction time with the syncFlush argument (associé à Deflater#SYNC_FLUSH) pour décider s'il faut vider les données en attente à compresser. Cet argument syncFlush est également accepté par GZIPOutputStream au moment de la construction.

On dirait que vous voulez utiliser soit Deflator#SYNC_FLUSH ou peut-être même Deflater#FULL_FLUSH, mais, avant de creuser jusque-là, d'abord essayer de travailler avec the two-argument ou the four-argument GZIPOutputStream constructor et passer true pour l'argument syncFlush. Cela activera le comportement de rinçage que vous désirez.

+0

Salut, ta réponse est super si tu travailles avec Java7 qui n'est pas sorti pour le moment. Je travaille avec java6 (comme le font la plupart des utilisateurs). – Hemeroc

+0

Oh, je suis désolé pour ça. Vous avez raison: ces signatures ne sont pas encore disponibles en Java 6. Cela me permet de lire la "dernière" documentation. Nous devrons attendre que cela arrive. – seh

1

Bug ID 4813885 gère ce problème. Le commentaire de "DamonHD", soumis le 9 septembre 2006 (environ à mi-chemin du rapport de bug) contient un exemple de FlushableGZIPOutputStream qu'il a construit au-dessus de Jazzlib'snet.sf.jazzlib.DeflaterOutputStream.

Pour référence, voici un (reformaté) Extrait:

/** 
* Substitute for GZIPOutputStream that maximises compression and has a usable 
* flush(). This is also more careful about its output writes for efficiency, 
* and indeed buffers them to minimise the number of write()s downstream which 
* is especially useful where each write() has a cost such as an OS call, a disc 
* write, or a network packet. 
*/ 
public class FlushableGZIPOutputStream extends net.sf.jazzlib.DeflaterOutputStream { 
    private final CRC32 crc = new CRC32(); 
    private final static int GZIP_MAGIC = 0x8b1f; 
    private final OutputStream os; 

    /** Set when input has arrived and not yet been compressed and flushed downstream. */ 
    private boolean somethingWritten; 

    public FlushableGZIPOutputStream(final OutputStream os) throws IOException { 
     this(os, 8192); 
    } 

    public FlushableGZIPOutputStream(final OutputStream os, final int bufsize) throws IOException { 
     super(new FilterOutputStream(new BufferedOutputStream(os, bufsize)) { 
      /** Suppress inappropriate/inefficient flush()es by DeflaterOutputStream. */ 
      @Override 
      public void flush() { 
      } 
     }, new net.sf.jazzlib.Deflater(net.sf.jazzlib.Deflater.BEST_COMPRESSION, true)); 
     this.os = os; 
     writeHeader(); 
     crc.reset(); 
    } 

    public synchronized void write(byte[] buf, int off, int len) throws IOException { 
     somethingWritten = true; 
     super.write(buf, off, len); 
     crc.update(buf, off, len); 
    } 

    /** 
    * Flush any accumulated input downstream in compressed form. We overcome 
    * some bugs/misfeatures here so that: 
    * <ul> 
    * <li>We won't allow the GZIP header to be flushed on its own without real compressed 
    * data in the same write downstream. 
    * <li>We ensure that any accumulated uncompressed data really is forced through the 
    * compressor. 
    * <li>We prevent spurious empty compressed blocks being produced from successive 
    * flush()es with no intervening new data. 
    * </ul> 
    */ 
    @Override 
    public synchronized void flush() throws IOException { 
     if (!somethingWritten) { return; } 

     // We call this to get def.flush() called, 
     // but suppress the (usually premature) out.flush() called internally. 
     super.flush(); 

     // Since super.flush() seems to fail to reliably force output, 
     // possibly due to over-cautious def.needsInput() guard following def.flush(), 
     // we try to force the issue here by bypassing the guard. 
     int len; 
     while((len = def.deflate(buf, 0, buf.length)) > 0) { 
      out.write(buf, 0, len); 
     } 

     // Really flush the stream below us... 
     os.flush(); 

     // Further flush()es ignored until more input data data written. 
     somethingWritten = false; 
    } 

    public synchronized void close() throws IOException { 
     if (!def.finished()) { 
      def.finish(); 
      do { 
       int len = def.deflate(buf, 0, buf.length); 
       if (len <= 0) { 
        break; 
       } 
       out.write(buf, 0, len); 
      } while (!def.finished()); 
     } 

     // Write trailer 
     out.write(generateTrailer()); 

     out.close(); 
    } 

    // ... 
} 

Vous trouverez peut-être utile.

+0

Salut, je m'attends aux mêmes problèmes que nardian, des suggestions? – Hemeroc

9

Je n'ai pas trouvé l'autre réponse au travail. Il refusait toujours de vider car le code natif utilisé par GZIPOutputStream tenait aux données.

Heureusement, j'ai découvert que quelqu'un a implémenté un FlushableGZIPOutputStream dans le cadre du projet Apache Tomcat. Voici la partie magique:

@Override 
public synchronized void flush() throws IOException { 
    if (hasLastByte) { 
     // - do not allow the gzip header to be flushed on its own 
     // - do not do anything if there is no data to send 

     // trick the deflater to flush 
     /** 
     * Now this is tricky: We force the Deflater to flush its data by 
     * switching compression level. As yet, a perplexingly simple workaround 
     * for 
     * http://developer.java.sun.com/developer/bugParade/bugs/4255743.html 
     */ 
     if (!def.finished()) { 
      def.setLevel(Deflater.NO_COMPRESSION); 
      flushLastByte(); 
      flagReenableCompression = true; 
     } 
    } 
    out.flush(); 
} 

Vous pouvez trouver toute la classe dans ce pot (si vous utilisez Maven):

<dependency> 
    <groupId>org.apache.tomcat</groupId> 
    <artifactId>tomcat-coyote</artifactId> 
    <version>7.0.8</version> 
</dependency> 

Ou tout simplement aller récupérer le code source FlushableGZIPOutputStream.java

Il est publié sous la licence Apache-2.0.

+1

Je reçois 'java.lang.IllegalStateException: setLevel ne peut pas être appelé après setInput' sous Android – 18446744073709551615

+0

got it:' nouveau GZIPOutputStream (...) {{setSyncFlush (true);}}; '(double accolades car il est un initialiseur d'instance à l'intérieur d'une classe sans nom) – 18446744073709551615

0

Il y a le même problème sur Android également. La réponse Accepter ne fonctionne pas car def.setLevel(Deflater.NO_COMPRESSION); émet une exception. Selon la méthode flush, il modifie le niveau de compression de Deflater. Donc, je suppose que le changement de compression devrait être appelé avant d'écrire des données, mais je ne suis pas sûr.

Il sont 2 autres options:

  • si le niveau de l'API de votre application est plus élevé que 19 vous pouvez alors essayer d'utiliser constructeur with syncFlush param
  • l'autre solution utilise jzlib.
+0

jzlib n'a pas fonctionné pour moi – 18446744073709551615

1

Ce code fonctionne très bien pour moi dans mon application.

public class StreamingGZIPOutputStream extends GZIPOutputStream { 

    public StreamingGZIPOutputStream(OutputStream out) throws IOException { 
     super(out); 
    } 

    @Override 
    protected void deflate() throws IOException { 
     // SYNC_FLUSH is the key here, because it causes writing to the output 
     // stream in a streaming manner instead of waiting until the entire 
     // contents of the response are known. for a large 1 MB json example 
     // this took the size from around 48k to around 50k, so the benefits 
     // of sending data to the client sooner seem to far outweigh the 
     // added data sent due to less efficient compression 
     int len = def.deflate(buf, 0, buf.length, Deflater.SYNC_FLUSH); 
     if (len > 0) { 
      out.write(buf, 0, len); 
     } 
    } 

} 
+0

J'ai eu exactement le même problème, et ceci a résolu mon problème gentiment! (le streaming vers un client est préférable de commencer plus tôt) – Tony