【问题标题】:Force flush on a GZIPOutputStream in java在 Java 中强制刷新 GZIPOutputStream
【发布时间】:2011-04-08 02:13:36
【问题描述】:

我们正在开发一个需要刷新(强制压缩和发送数据)GZIPOutputStream 的程序。问题是,GZIPOutputStream 的 flush 方法没有按预期工作(强制压缩和发送数据),而是 Stream 等待更多数据以进行有效的数据压缩。

当您调用完成时,数据被压缩并通过输出流发送,但 GZIPOutputStream(不是底层流)将被关闭,因此在创建新的 GZIPOutputStream 之前我们无法写入更多数据,这会耗费时间和性能。

希望任何人都可以提供帮助。

最好的问候。

【问题讨论】:

    标签: java gzip gzipoutputstream


    【解决方案1】:

    我还没有尝试过,这个建议在我们手头有 Java 7 之前不会有用,但是从 DeflaterOutputStream 继承的 GZIPOutputStreamflush() 方法的文档依赖于 flush模式在构造时指定the syncFlush argument(与Deflater#SYNC_FLUSH相关)来决定是否刷新待压缩的待压缩数据。这个syncFlush 参数在构造时也被GZIPOutputStream 接受。

    听起来您想使用Deflator#SYNC_FLUSH 或者甚至Deflater#FULL_FLUSH,但是,在深入挖掘之前,首先尝试使用the two-argumentthe four-argument GZIPOutputStream constructor 并通过true 获得syncFlush争论。这将激活您想要的冲洗行为。

    【讨论】:

    • 您好,如果您正在使用目前尚未发布的 Java7,您的回答非常好。我正在使用 java6(就像大多数用户一样)。
    • 哦,我很抱歉。你是对的:这些签名在 Java 6 中尚不可用。这对我阅读“最新”文档很有帮助。我们将不得不等待这些到达。
    【解决方案2】:

    Bug ID 4813885 处理这个问题。 2006 年 9 月 9 日提交的“DamonHD”评论(大约是错误报告的一半)包含一个 FlushableGZIPOutputStream 的示例,他在 Jazzlib's net.sf.jazzlib.DeflaterOutputStream 之上构建。

    作为参考,这是一个(重新格式化的)摘录:

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

    您可能会发现它很有用。

    【讨论】:

    • 嗨,我预计和 nardian 有同样的问题,有什么建议吗?
    【解决方案3】:

    我没有找到其他可行的答案。它仍然拒绝刷新,因为 GZIPOutputStream 使用的本机代码保留了数据。

    谢天谢地,我发现有人在 Apache Tomcat 项目中实现了 FlushableGZIPOutputStream。这是神奇的部分:

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

    你可以在这个 jar 中找到整个类(如果你使用 Maven):

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

    或者直接去获取源代码FlushableGZIPOutputStream.java

    它是在 Apache-2.0 许可下发布的。

    【讨论】:

    • 我在Android下得到java.lang.IllegalStateException: setLevel cannot be called after setInput
    • 知道了:new GZIPOutputStream(...) {{setSyncFlush(true);}};(双括号,因为它是无名类中的实例初始化器)
    【解决方案4】:

    Android 也有同样的问题。接受者回答不起作用,因为def.setLevel(Deflater.NO_COMPRESSION); 抛出异常。根据flush 方法,它改变了Deflater 的压缩级别。所以我认为应该在写入数据之前调用更改压缩,但我不确定。

    还有 2 个其他选项:

    • 如果您的应用程序的 API 级别高于 19,那么您可以尝试使用构造函数with syncFlush param
    • 另一种解决方案是使用jzlib

    【讨论】:

      【解决方案5】:

      这段代码在我的应用程序中非常适合我。

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

      【讨论】:

      • 我遇到了完全相同的问题,这很好地解决了我的问题! (向客户端的流式传输最好尽早开始)
      • 这与在GZIPOutputStream 构造函数中将syncFlush 设置为true 有何不同?
      【解决方案6】:

      正如@seh 所说,这很好用:

      ByteArrayOutputStream stream = new ByteArrayOutputStream();
      
      // the second param need to be true
      GZIPOutputStream gzip = new GZIPOutputStream(stream,  true);
      gzip.write( .. );
      gzip.flush();
      
      ...
      gzip.close()
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-12-25
        • 2017-10-04
        • 1970-01-01
        • 2010-10-18
        • 2013-01-03
        • 2010-12-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多