使用 apache common compress/org.tukaani.xz 在 java 中解码 LZMA 压缩 zip 文件的问题

Posted

技术标签:

【中文标题】使用 apache common compress/org.tukaani.xz 在 java 中解码 LZMA 压缩 zip 文件的问题【英文标题】:Issue with decoding LZMA compress zip file in java using apache common compress/org.tukaani.xz 【发布时间】:2017-07-20 11:46:30 【问题描述】:

在尝试解码 LZMA 压缩 xls 文件时出现 org.tukaani.xz.UnsupportedOptionsException: Uncompressed size is too big 错误。而非 LZMA 文件可以毫无问题地解包/解码。两种情况下同一个 xls 文件被压缩。

我正在使用 Apache commons compress 和 org.tukaani.xz。

示例代码供参考

package com.concept.utilities.zip;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream;

public class ApacheComm 

    public void extractLZMAZip(File zipFile, String compressFileName, String destFolder) 

        ZipFile zip = null;
        try 

            zip = new ZipFile(zipFile);
            ZipArchiveEntry zipArchiveEntry = zip.getEntry(compressFileName);
            if (null != zipArchiveEntry) 
                String name = zipArchiveEntry.getName();

                // InputStream is = zip.getInputStream(zipArchiveEntry);
                InputStream israw = zip.getRawInputStream(zipArchiveEntry);

                LZMACompressorInputStream lzma = new LZMACompressorInputStream(israw);
            

         catch (IOException e) 
            e.printStackTrace();
         finally 
            if (null != zip)
                ZipFile.closeQuietly(zip);
        
    

    public static void main(String[] args) throws IOException 

        ApacheComm c = new ApacheComm();
        try 
            c.extractLZMAZip(new File("H:\\archives\\rollLZMA.zip"), "roll.xls", "H:\\archives\\");
         catch (Exception e) 
            e.printStackTrace();
        

    


错误

org.tukaani.xz.UnsupportedOptionsException: Uncompressed size is too big
    at org.tukaani.xz.LZMAInputStream.initialize(Unknown Source)
    at org.tukaani.xz.LZMAInputStream.<init>(Unknown Source)
    at org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream.<init>(LZMACompressorInputStream.java:50)
    at com.concept.utilities.zip.ApacheComm.extractLZMAZip(ApacheComm.java:209)
    at com.concept.utilities.zip.ApacheComm.main(ApacheComm.java:224)

我错过了什么吗?有没有其他方法可以解码 zip 文件与压缩方法 = LZMA

【问题讨论】:

您要解码的文件有多大? 为了测试我使用的是小 xls 文件。有两列和三行,包括标题。 26KB。 压缩文件本身有多大? 3 KB。 Zip 文件包含一个 xls。 【参考方案1】:

您的代码不起作用的原因是 Zip LZMA 压缩数据段与普通压缩 LZMA 文件相比具有不同的标头。

您可以在https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT 阅读规范(4.4.4 通用位标志,5.8 LZMA - 方法 14),但要引用重要部分:

5.8.5 [...] LZMA 压缩数据段由 LZMA 属性标头和 LZMA 压缩数据组成,如下所示:

[LZMA properties header for file 1]
[LZMA compressed data for file 1]

[...]

5.8.8 LZMA Properties Header中属性信息的存储字段如下:

LZMA Version Information 2 bytes
LZMA Properties Size 2 bytes
LZMA Properties Data variable, defined by "LZMA Properties Size"

5.8.8.1 LZMA 版本信息 - 此字段标识用于压缩文件的 LZMA SDK 版本。第一个字节存储LZMA SDK的主版本号,第二个字节存储次版本号。

5.8.8.2 LZMA 属性大小 - 此字段定义剩余属性数据的大小。通常这个大小应该由 SDK 的版本决定。包含此大小字段是为了方便并帮助避免将来由于此压缩算法的更改而出现任何歧义。

5.8.8.3 LZMA 属性数据 - 此可变大小字段记录 LZMA SDK 定义的解压缩器所需的值。该字段中存储的数据应该使用“LZMA版本信息”字段定义的SDK版本中的WriteCoderProperties()获取。

代码示例:

import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.apache.commons.compress.archivers.zip.ZipMethod;
import org.apache.commons.io.IOUtils;
import org.tukaani.xz.LZMAInputStream;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class ApacheComm

    public InputStream getInputstreamForEntry(ZipFile zipFile, ZipArchiveEntry ze) throws IOException
    
        if (zipFile.canReadEntryData(ze))
        
            return zipFile.getInputStream(ze);
         else if (ze.getMethod() == ZipMethod.LZMA.getCode()) 
            InputStream inputStream = zipFile.getRawInputStream(ze);
            ByteBuffer buffer = ByteBuffer.wrap(IOUtils.readFully(inputStream, 9))
                    .order(ByteOrder.LITTLE_ENDIAN);

            // Lzma sdk version used to compress this data
            int majorVersion = buffer.get();
            int minorVersion = buffer.get();

            // Byte count of the following data represent as an unsigned short.
            // Should be = 5 (propByte + dictSize) in all versions
            int size = buffer.getShort() & 0xffff;
            if (size != 5)
                throw new UnsupportedOperationException();

            byte propByte = buffer.get();

            // Dictionary size is an unsigned 32-bit little endian integer.
            int dictSize = buffer.getInt();

            long uncompressedSize;
            if ((ze.getRawFlag() & (1 << 1)) != 0)
            
                // If the entry uses EOS marker, use -1 to indicate
                uncompressedSize = -1;
             else 
                uncompressedSize = ze.getSize();
            

            return new LZMAInputStream(inputStream, uncompressedSize, propByte, dictSize);
         else 
            throw new UnsupportedOperationException();
        
    

【讨论】:

非常感谢这段代码!我得到了一个 UnsupportedZipFeatureException 例外,只有基本的 BufferedInputStream 和 Apache Common 的 Compress(解码使用 Win 10 的压缩器创建的 zip,显然其中的随机文件使用 LZMA,而大多数不使用)。使用他们的“LZMA”code 只是抛出了操作的异常:Uncompressed size is too big 但现在一切正常! :)

以上是关于使用 apache common compress/org.tukaani.xz 在 java 中解码 LZMA 压缩 zip 文件的问题的主要内容,如果未能解决你的问题,请参考以下文章

Flink SQL ClassNotFoundException: org.apache.commons.compress.compressors.zstandard.ZstdCompressorIn

程序员的福音 - Apache Commons Compress

在 Linux 上使用 Apache Commons Compression 压缩文件时出现编码错误

Java解压tar.Z文件(使用Apache Commons-compress)

commons-compress(apache压缩工具包)

Apache Commons Compress 无法在 tar 中添加硬链接