【问题标题】:Does a GunzipOutputStream - or something like it - exist?GunzipOutputStream - 或类似的东西 - 是否存在?
【发布时间】:2012-08-08 22:34:19
【问题描述】:

Handling HTTP ContentEncoding "deflate" 相关,我想知道如何使用OutputStream 来膨胀gzipdeflate 流。原因如下:

我有一个从 Web 服务器获取资源的类(想想wget,但在 Java 中)。我有它严格执行响应的内容长度,我想保持这种执行。所以,我想做的是从响应中读取特定数量的字节(我已经这样做了),但如果响应已被压缩,它会生成更多字节。

我有这样的deflate回复工作:

OutputStream out = System.out;
out = new InflateOutputStream(out);
// repeatedly:
out.write(compressedBytesFromResponse);

我希望能够对 gzip 响应做同样的事情,但是没有 GunzipOutputStream,我不知道下一步该做什么。

更新

我正在考虑构建类似this 的东西,但它似乎完全疯了。也许这是使用OutputStream 来扩充我的数据的唯一方法。

【问题讨论】:

  • 尊敬的@AdamZalcman,这个问题是关于使用GZIPInputStream。我正在寻找可以解压缩数据但作为 OutputStream 存在的东西。

标签: java http gzip http-compression content-encoding


【解决方案1】:

对于deflate,Java 有InflaterOutputStream 可以满足您的需求:提供压缩的压缩数据并将未压缩的数据发送到其底层输出流。


对于gzip... 似乎找不到等价物。 InflaterOutputStream 的伙伴 InflaterInputStream 有一个处理所有标头的 GZipInputStream 子类,但没有等效的解压缩输出流类可能是 InflaterOutputStream 的子类。

自己为 GZIP 构建 InflaterOutputStream 的子类看起来很麻烦,查看 GZipInputStream 的源代码(处理标题、预告片等)

使用管道流似乎是两害相权取其轻。

【讨论】:

  • 是的,在我编写自己的“本机”GunzipOutputStream 之前,我将使用一对Piped(Input|Output)Streams 和一个泵线程。
【解决方案2】:

回答我自己的问题:

这里有两种可能性:gunzip 输出(例如使用GunzipOutputStream,Java API 未提供),或 gunzip 输入(例如,使用GZIPInputStream,Java API 提供)plus 在读取期间强制执行 Content-Length。

我两者都做过,而且我认为我更喜欢后者,因为 a) 它不需要启动单独的线程来将字节从 PipedOutputStream 泵送到 PipedIOnputStream 和 b)(我猜是必然结果)它没有竞争条件和其他同步问题的威胁。

首先,这是我对LimitedInputStream 的实现,它允许我包装输入流并强制限制读取的数据量。请注意,我还有一个 BigLimitedInputStream 使用 BigInteger 计数来支持大于 Long.MAX_LONG 的 Content-Length 值:

public class LimitedInputStream
    extends InputStream
{
    private long _limit;
    private long _read;
    private InputStream _in;

    public LimitedInputStream(InputStream in, long limit)
    {
        _limit= limit;
        _in = in;
        _read = 0;
    }
    @Override
    public int available()
        throws IOException
    {
        return _in.available(); // sure?
    }

    @Override
    public void close()
        throws IOException
    {
        _in.close();
    }

    @Override
    public boolean markSupported()
    {
        return false;
    }

    @Override
    public int read()
        throws IOException
    {
        int read = _in.read();

        if(-1 == read)
            return -1;

        ++_read;

        if(_read > _limit)
            return -1;
            // throw new IOException("Read limit reached: " + _limit);

        return read;
    }

    @Override
    public int read(byte[] b)
        throws IOException
    {
        return read(b, 0, b.length);
    }

    @Override
    public int read(byte[] b, int off, int len)
        throws IOException
    {
        // 'len' is an int, so 'max' is an int; narrowing cast is safe
        int max = (int)Math.min((long)(_limit - _read), (long)len);

        if(0 == max && len > 0)
            return -1;
            //throw new IOException("Read limit reached: " + _limit);

        int read = _in.read(b, off, max);

        _read += read;

        // This should never happen
        if(_read > _limit)
            return -1;
            //throw new IOException("Read limit reached: " + _limit);

        return read;
    }

    @Override
    public long skip(long n)
        throws IOException
    {
        long max = Math.min((long)(_limit - _read), n);

        if(0 == max)
            return 0;

        long read = _in.skip(max);

        _read += read;

        return read;
    }
}

使用上面的类来包装从HttpURLConnection 获得的InputStream 允许我简化现有代码,我必须读取Content-Length 标头中提到的精确字节数,并且只是盲目地将输入复制到输出。然后我将输入流(已经在 LimitedInputStream 中包装)包装在 GZIPInputStream 中以进行解压缩,然后将字节从(双重包装的)输入泵送到输出。

不太直截了当的路线是继续我原来的路线:使用(结果是)一个尴尬的类来包装 OutputStream:GunzipOutputStream。我写了一个GunzipOutputStream,它使用一个内部线程通过一对管道流来泵送字节。它很丑,它基于来自OpenRDF's GunzipOutputStream 的代码。我觉得我的比较简单:

public class GunzipOutputStream
    extends OutputStream
{
    final private Thread _pump;

    // Streams
    final private PipedOutputStream _zipped;  // Compressed bytes are written here (by clients)
    final private PipedInputStream _pipe; // Compressed bytes are read (internally) here
    final private OutputStream _out; // Uncompressed data is written here (by the pump thread)

    // Internal state
    private IOException _e;

    public GunzipOutputStream(OutputStream out)
        throws IOException
    {
        _zipped = new PipedOutputStream();
        _pipe = new PipedInputStream(_zipped);
        _out = out;
        _pump = new Thread(new Runnable() {
            public void run() {
                InputStream in = null;
                try
                {
                    in = new GZIPInputStream(_pipe);

                    pump(in, _out);
                }
                catch (IOException e)
                {
                    _e = e;
                    System.err.println(e);
                    _e.printStackTrace();
                }
                finally
                {
                    try { in.close(); } catch (IOException ioe)
                    { ioe.printStackTrace(); }
                }
            }

            private void pump(InputStream in, OutputStream out)
                throws IOException
            {
                long count = 0;

                byte[] buf = new byte[4096];

                int read;
                while ((read = in.read(buf)) >= 0) {
                    System.err.println("===> Pumping " + read + " bytes");
                    out.write(buf, 0, read);
                    count += read;
                }
                out.flush();
                System.err.println("===> Pumped a total of " + count + " bytes");
            }
        }, "GunzipOutputStream stream pump " + GunzipOutputStream.this.hashCode());

        _pump.start();
    }

    public void close() throws IOException {
        throwIOException();
        _zipped.close();
        _pipe.close();
        _out.close();
    }

    public void flush() throws IOException {
        throwIOException();
        _zipped.flush();
    }

    public void write(int b) throws IOException {
        throwIOException();
        _zipped.write(b);
    }

    public void write(byte[] b) throws IOException {
        throwIOException();
        _zipped.write(b);
    }

    public void write(byte[] b, int off, int len) throws IOException {
        throwIOException();
        _zipped.write(b, off, len);
    }

    public String toString() {
        return _zipped.toString();
    }

    protected void finish()
        throws IOException
    {
        try
        {
            _pump.join();
            _pipe.close();
            _zipped.close();
        }
        catch (InterruptedException ie)
        {
            // Ignore
        }
    }

    private void throwIOException()
        throws IOException
    {
        if(null != _e)
        {
            IOException e = _e;
            _e = null; // Clear the existing error
            throw e;
        }
    }
}

再一次,这行得通,但它似乎相当……脆弱。

最后,我重构了我的代码以使用LimitedInputStreamGZIPInputStream 而没有使用GunzipOutputStream。如果 Java API 提供了GunzipOutputStream,那就太好了。但事实并非如此,而且如果不编写“本机”gunzip 算法,实现您自己的GunzipOutputStream 就会扩展适当的限制。

【讨论】:

    【解决方案3】:

    如果你使用 HttpURLConnection,所有这些事情都会自动发生。

    【讨论】:

    • 奇怪的是,我使用的是HttpURLConnection,不,它不会自动发生。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-22
    • 2017-12-08
    • 2015-09-25
    • 2011-12-18
    • 2014-02-15
    相关资源
    最近更新 更多