【问题标题】:Gets the uncompressed size of this GZIPInputStream?获取此 GZIPInputStream 的未压缩大小?
【发布时间】:2011-09-06 08:50:41
【问题描述】:

我有一个GZIPInputStream,它是从另一个ByteArrayInputStream 构建的。我想知道 gzip 数据的原始(未压缩)长度。虽然我可以读到GZIPInputStream的末尾,然后数一下,这会花费很多时间并浪费CPU。在阅读之前我想知道它的大小。

有没有类似ZipEntry.getSize() for GZIPInputStream的方法:

public long getSize ()
自: API 级别 1
获取此 ZipEntry 的未压缩大小。

【问题讨论】:

  • 请注意,GZIP 仅保护模 2^32 的大小(即,它仅将大小的低 32 位存储在名为 ISIZE 的字段中)。 如果您的数据可能大于 4 GB,那么这些信息对您没有帮助。
  • 继续这种方式,有两个其他的原因,最后四个字节不是未压缩数据的可靠度量,即使对于小文件也是如此。唯一可靠的方法是解压流并计算字节数。

标签: java gzip gzipinputstream


【解决方案1】:

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

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

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 时,您无法确定正确的未压缩大小!

【讨论】:

  • 根据original GZIP format specification:“一个gzip文件由一系列“成员”(压缩数据集)组成。每个成员的格式在下一节中指定。成员只是出现一个文件中一个接一个,在它们之前、之间或之后没有其他信息。”因此,如果您的 gzip 文件包含多个“成员”,则您只读取这四个字节中最后一个“成员”的大小。
  • 如果你知道你只有一个“成员”,那么我想这将是一个可以接受的答案。
【解决方案2】:

基于@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() 小于最终未压缩大小。
【解决方案3】:

有没有类似 ZipEntry.getSize() 的方法 GZIPInputStream

没有。它不在Javadoc => 它不存在。

你需要什么长度

【讨论】:

  • 我倾向于同意这一点。甚至 GZip 文档也声明它无法找到所有文件的未压缩大小 - gnu.org/software/gzip/manual/gzip.html#Invoking-gzip。您可以使用 --list 来获得未压缩的大小,但这可能会“浪费”与使用 Java 读取相同的 CPU。
  • 再想想,对我来说似乎没用。
  • 我正在为一本电子书工作(Gzip 格式)。每章都是GZIP,我想知道本书的总长度,用于计算阅读百分比。
  • @David Guo 对压缩后的长度进行计算可能就足够准确了。
【解决方案4】:

如果您可以猜测压缩比(如果数据与您已经处理过的其他数据相似,这是一个合理的预期),那么您可以计算出任意大文件的大小(有一些错误)。同样,这假定文件包含单个 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 的回答]

【讨论】:

    【解决方案5】:

    基于 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;
    }
    

    【讨论】:

      【解决方案6】:

      除了解压缩整个内容之外,没有可靠的方法来获取长度。见Uncompressed file size using zlib's gzip file access function

      【讨论】:

        【解决方案7】:

        改为从底层 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);
                    }
                }
            }
        }
        

        【讨论】:

          【解决方案8】:

          不,不幸的是,如果您想获得未压缩的大小,则必须读取整个流并增加一个计数器,就像您在问题中提到的那样。为什么需要知道尺寸?大小估计是否适合您的目的?

          【讨论】:

            猜你喜欢
            • 2016-02-08
            • 1970-01-01
            • 1970-01-01
            • 2010-10-30
            • 1970-01-01
            • 2019-08-13
            • 2018-10-19
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多