获取此GZIPInputStream的未压缩大小?

12
我有一个由另一个ByteArrayInputStream构造的GZIPInputStream。 我想知道gzip数据的原始(未压缩)长度。虽然我可以读取到GZIPInputStream的结尾,然后计算数量,但这将耗费很多时间并浪费CPU。 我想在读取之前知道大小。
是否存在类似于ZipEntry.getSize()的方法适用于GZIPInputStream:
``` public long getSize() Since: API Level 1 获取此ZipEntry的未压缩大小。 ```

请注意,GZIP只能保存模数为2 ^ 32的大小(即它仅存储大小的低32位,存储在名为ISIZE的字段中)。 如果您的数据可能大于4 GB,则此信息将对您没有帮助。 - Joachim Sauer
继续这个话题,还有两个其他原因导致最后四个字节不是压缩数据的可靠度量单位,即使对于小文件也是如此。唯一可靠的方法是解压流并计算字节数。 - Mark Adler
8个回答

9

通过读取gzipped文件的最后四个字节,可以确定未压缩大小。

我在这里找到了这个解决方案:

http://www.abeel.be/content/determine-uncompressed-size-gzip-file

此链接还提供了一些示例代码(已更正为使用 long 而不是 int ,以处理大小在2GB和4GB之间的情况,否则 int 将会回绕):

RandomAccessFile raf = new RandomAccessFile(file, "r");
raf.seek(raf.length() - 4);
byte b4 = raf.read();
byte b3 = raf.read();
byte b2 = raf.read();
byte b1 = raf.read();
long val = ((long)b1 << 24) | ((long)b2 << 16) | ((long)b3 << 8) | (long)b4;
raf.close();

val 是以字节为单位的长度。注意:当未压缩的文件大于4GB时,您无法确定正确的未压缩大小!


2
根据原始GZIP格式规范:"gzip文件由一系列“成员”(压缩数据集)组成。每个成员的格式在下一节中指定。这些成员只是一个接着一个地出现在文件中,在它们之前、之间或之后没有其他信息。"因此,如果您的gzip文件包含多个“成员”,则您只需读取这四个字节中最后一个“成员”的大小。 - Oleg Muravskiy
如果你知道你只有一个“成员”,那么我想这个答案是可以接受的。 - rollsch

7
基于@Alexander的回答:
RandomAccessFile raf = new RandomAccessFile(inputFilePath + ".gz", "r");
raf.seek(raf.length() - 4);
byte[] bytes = new byte[4];
raf.read(bytes);
fileSize = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt();
if (fileSize < 0)
  fileSize += (1L << 32);
raf.close();

工作正常,但我发现返回的长度几乎完全是压缩文件的.length()比最终未压缩大小小。 - hunterp

2

如果你能猜测压缩比(如果数据类似于您已经处理过的其他数据,则这是一个合理的期望),那么您可以计算出任意大文件的大小(带有一些误差)。同样,这假设文件包含单个gzip流。以下假设第一个大小大于估计大小(基于估计比率)的90%是真实大小:

estCompRatio = 6.1;
RandomAccessFile raf = new RandomAccessFile(inputFilePath + ".gz", "r");
compLength = raf.length();
byte[] bytes = new byte[4];
raf.read(bytes);
uncLength = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt();
raf.seek(compLength - 4);
uncLength = raf.readInt();
while(uncLength < (compLength * estCompRatio * 0.9)){
  uncLength += (1L << 32);
}

将 estCompRatio 设置为 0 相当于 @Alexander 的答案。

2

这是一个基于4个尾部字节进行计算的更紧凑版本(避免使用字节缓冲区,并调用 Integer.reverseBytes 来反转读取字节的顺序)。

private static long getUncompressedSize(Path inputPath) throws IOException
{
    long size = -1;
    try (RandomAccessFile fp = new RandomAccessFile(inputPath.toFile(), "r")) {        
        fp.seek(fp.length() - Integer.BYTES);
        int n = fp.readInt();
        size = Integer.toUnsignedLong(Integer.reverseBytes(n));
    }
    return size;
}

2

是否有类似于ZipEntry.getSize()的方法可用于GZIPInputStream?

没有。在Javadoc中没有相关内容,因此该方法不存在。

您需要这个长度信息来做什么?


我倾向于同意这个观点。即使是GZip文档也指出它无法为所有文件找到未压缩大小 - http://www.gnu.org/software/gzip/manual/gzip.html#Invoking-gzip。您可以使用“--list”获取未压缩大小,但这可能会“浪费”与使用Java读取相同的CPU。 - Paul Grime
重新考虑后,对我来说似乎没有用处。 - David Guo
1
我正在为一本电子书(Gzip格式)工作。每个章节都是一个GZIP文件,我想知道整本书的总长度以计算阅读百分比。 - David Guo
@David Guo 对压缩长度进行计算可能已经足够准确了。 - user207421

2

0
从底层的FileInputStream中获取FileChannel。它会告诉你压缩文件的大小和当前位置。示例:
@Override
public void produce(final DataConsumer consumer, final boolean skipData) throws IOException {
    try (FileInputStream fis = new FileInputStream(tarFile)) {
        FileChannel channel = fis.getChannel();
        final Eta<Long> eta = new Eta<>(channel.size());
        try (InputStream is = tarFile.getName().toLowerCase().endsWith("gz")
            ? new GZIPInputStream(fis) : fis) {
            try (TarArchiveInputStream tais = (TarArchiveInputStream) new ArchiveStreamFactory()
                .createArchiveInputStream("tar", new BufferedInputStream(is))) {

                TarArchiveEntry tae;
                boolean done = false;
                while (!done && (tae = tais.getNextTarEntry()) != null) {
                    if (tae.getName().startsWith("docs/") && tae.getName().endsWith(".html")) {
                        String data = null;
                        if (!skipData) {
                            data = new String(tais.readNBytes((int) tae.getSize()), StandardCharsets.UTF_8);
                        }
                        done = !consumer.consume(data);
                    }

                    String progress = eta.toStringPeriodical(channel.position());
                    if (progress != null) {
                        System.out.println(progress);
                    }
                }
                System.out.println("tar bytes read: " + tais.getBytesRead());
            } catch (ArchiveException ex) {
                throw new IOException(ex);
            }
        }
    }
}

-1
不好意思,如果你想获取未压缩的大小,你必须读取整个流并像你在问题中提到的那样递增计数器。你为什么需要知道大小?估计大小是否能满足你的需求?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接