【问题标题】:Deflate (ZIP) compressing on the fly in Java using InputStream and OutputStream abstraction only. Possible?仅使用 InputStream 和 OutputStream 抽象在 Java 中进行 Deflate (ZIP) 压缩。可能的?
【发布时间】:2012-02-12 11:33:41
【问题描述】:

我目前正在尝试编写一个自定义流代理(让我们以这种方式调用它),它可以更改给定输入流的内容,并在必要时生成修改后的输出。这个要求真的很有必要,因为有时我必须修改我的应用程序中的流(例如,真正即时压缩数据)。下面的类非常简单,它使用内部缓冲。

private static class ProxyInputStream extends InputStream {

    private final InputStream iStream;
    private final byte[] iBuffer = new byte[512];

    private int iBufferedBytes;

    private final ByteArrayOutputStream oBufferStream;
    private final OutputStream oStream;

    private byte[] oBuffer = emptyPrimitiveByteArray;
    private int oBufferIndex;

    ProxyInputStream(InputStream iStream, IFunction<OutputStream, ByteArrayOutputStream> oStreamFactory) {
        this.iStream = iStream;
        oBufferStream = new ByteArrayOutputStream(512);
        oStream = oStreamFactory.evaluate(oBufferStream);
    }

    @Override
    public int read() throws IOException {
        if ( oBufferIndex == oBuffer.length ) {
            iBufferedBytes = iStream.read(iBuffer);
            if ( iBufferedBytes == -1 ) {
                return -1;
            }
            oBufferIndex = 0;
            oStream.write(iBuffer, 0, iBufferedBytes);
            oStream.flush();
            oBuffer = oBufferStream.toByteArray();
            oBufferStream.reset();
        }
        return oBuffer[oBufferIndex++];
    }

}

假设我们还有一个示例测试输出流,它只是在每个写入的字节(“abc”->“a b c”)之前添加一个空格字符,如下所示:

private static class SpacingOutputStream extends OutputStream {

    private final OutputStream outputStream;

    SpacingOutputStream(OutputStream outputStream) {
        this.outputStream = outputStream;
    }

    @Override
    public void write(int b) throws IOException {
        outputStream.write(' ');
        outputStream.write(b);
    }

}

还有如下测试方法:

private static void test(final boolean useDeflater) throws IOException {
    final FileInputStream input = new FileInputStream(SOURCE);
    final IFunction<OutputStream, ByteArrayOutputStream> outputFactory = new IFunction<OutputStream, ByteArrayOutputStream>() {
        @Override
        public OutputStream evaluate(ByteArrayOutputStream outputStream) {
            return useDeflater ? new DeflaterOutputStream(outputStream) : new SpacingOutputStream(outputStream);
        }
    };
    final InputStream proxyInput = new ProxyInputStream(input, outputFactory);
    final OutputStream output = new FileOutputStream(SOURCE + ".~" + useDeflater);
    int c;
    while ( (c = proxyInput.read()) != -1 ) {
        output.write(c);
    }
    output.close();
    proxyInput.close();
}

此测试方法只是读取文件内容并将其写入另一个流,这可能可以以某种方式进行修改。如果测试方法使用useDeflater=false 运行,则预期的方法可以正常工作。但是如果在 useDeflater 设置为 on 的情况下调用测试方法,它的行为会非常奇怪并且几乎什么都不写(如果省略标题 78 9C)。我怀疑 deflate 类的设计可能不符合我喜欢使用的方法,但我始终相信 ZIP 格式和 deflate 压缩旨在即时工作。

可能我在某些时候对 deflate 压缩算法的细节有误。我真的想念什么?..也许可以有另一种方法来编写“流代理”以完全按照我想要的方式工作...如何动态压缩数据,仅限于流?

提前致谢。


UPD:以下基本版本非常适合 deflater 和 inflater:

public final class ProxyInputStream<OS extends OutputStream> extends InputStream {

private static final int INPUT_BUFFER_SIZE = 512;
private static final int OUTPUT_BUFFER_SIZE = 512;

private final InputStream iStream;
private final byte[] iBuffer = new byte[INPUT_BUFFER_SIZE];
private final ByteArrayOutputStream oBufferStream;
private final OS oStream;
private final IProxyInputStreamListener<OS> listener;

private byte[] oBuffer = emptyPrimitiveByteArray;
private int oBufferIndex;
private boolean endOfStream;

private ProxyInputStream(InputStream iStream, IFunction<OS, ByteArrayOutputStream> oStreamFactory, IProxyInputStreamListener<OS> listener) {
    this.iStream = iStream;
    oBufferStream = new ByteArrayOutputStream(OUTPUT_BUFFER_SIZE);
    oStream = oStreamFactory.evaluate(oBufferStream);
    this.listener = listener;
}

public static <OS extends OutputStream> ProxyInputStream<OS> proxyInputStream(InputStream iStream, IFunction<OS, ByteArrayOutputStream> oStreamFactory, IProxyInputStreamListener<OS> listener) {
    return new ProxyInputStream<OS>(iStream, oStreamFactory, listener);
}

@Override
public int read() throws IOException {
    if ( oBufferIndex == oBuffer.length ) {
        if ( endOfStream ) {
            return -1;
        } else {
            oBufferIndex = 0;
            do {
                final int iBufferedBytes = iStream.read(iBuffer);
                if ( iBufferedBytes == -1 ) {
                    if ( listener != null ) {
                        listener.afterEndOfStream(oStream);
                    }
                    endOfStream = true;
                    break;
                }
                oStream.write(iBuffer, 0, iBufferedBytes);
                oStream.flush();
            } while ( oBufferStream.size() == 0 );
            oBuffer = oBufferStream.toByteArray();
            oBufferStream.reset();
        }
    }
    return !endOfStream || oBuffer.length != 0 ? (int) oBuffer[oBufferIndex++] & 0xFF : -1;
}

}

【问题讨论】:

  • 我有点失落。但是当我不想压缩时,我应该简单地使用原始的outputStream,而当我想要压缩时使用new GZipOutputStream(outputStream)。就这样。无论如何,请检查您是否正在刷新输出流。
  • ByteArrayOutputStream != BufferedOutputStream。非常喜欢。

标签: java stream zip deflate on-the-fly


【解决方案1】:

我不相信DeflaterOutputStream.flush() 做任何有意义的事情。 deflater 将累积数据,直到它有一些东西要写入底层流。强制剩余数据位的唯一方法是调用DeflaterOutputStream.finish()。但是,这不适用于您当前的实现,因为在您完全完成编写之前您不能调用完成。

实际上很难编写压缩流并在同一个线程中读取它。在RMIIO 项目中,我实际上是这样做的,但是您需要一个任意大小的中间输出缓冲区(并且您基本上需要将数据推入,直到另一端出现压缩,然后您才能读取它)。您也许可以使用该项目中的一些 util 类来完成您想做的事情。

【讨论】:

  • “你基本上需要将数据推入,直到另一端压缩出来”这是最大的问题之一(除非你有能力一次压缩整个内容);一个低效(但简单)的解决方案是将数据压缩成离散的“数据包”,前提是您能够进行解压缩。
  • 刚刚将带有void afterFlush(O outputStream) throws IOException; 的侦听器添加到该代码示例中。最后它压缩了“lorem ipsum”文本样本。感谢您指向.finish()。 :)
  • @LyubomyrShaydariv - 但是你意识到,一旦你调用完成,你的压缩流就完成了。您将永远无法处理超过 512 字节的压缩数据。您当前的代码并不是真正的“通用”解决方案。
  • 是的,我首先想到的是“是的,它有效”。但是,它仅适用于 446 长度的 Lorem ipsum... 文本等。当我将文本加倍压缩(892 b)时 - 它因您提到的原因而失败。无论如何,我们今天完全重新设计了read() 方法,最后它可以在放气机和充气机上运行。我只是说谢谢你指出我现在在输入流结束后调用的finish() 方法。问题中的源代码也错过了这样一个事实,即结果字节数组缓冲区中的 -1 被视为流的结尾,而不是实际的 0xFF 数据。
【解决方案2】:

为什么不使用 GZipOutputStream?

我有点失落。但是当我不想压缩时,我应该简单地使用原始的outputStream,而当我想要压缩时,我应该使用new GZipOutputStream(outputStream)。就这样。无论如何,请检查您是否正在刷新输出流。

Gzip 与 zip

另外:一件事是 GZIP(压缩流,这就是您正在做的事情),另一件事是编写有效的 zip 文件(文件头、文件目录、条目(头、数据)*)。检查ZipOutputStream。

【讨论】:

  • 感谢您的回复。使用 GZipOutputStream 和 ZipOutputStream 一样没有效果。我只是完全修剪了输出流:“Lorem ipsum ...”从 446 变成了我在问题中提到的两个字节。我不能直接使用 OutputStream,因为要求是让 InputStream 委托给 JDBC 准备语句(因为可能有大量传入数据)。这就是为什么我正在寻找一个像 MyApp(inputStream) --> [compressor] --> JDBC(inputStream) 这样的代理的类。
【解决方案3】:

小心,如果你在某处使用方法 int read(byte b[], int off, int len) 并且如果出现异常情况 final int iBufferedBytes = iStream.read(iBuffer);

你会陷入无限循环

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-11
    • 2016-01-06
    • 2021-11-28
    • 1970-01-01
    相关资源
    最近更新 更多