从临时目录读取 SQLite 文件时获取 java.io.EOFException

Getting java.io.EOFException while reading a SQLite file from temp directory

我在从临时目录读取 SQLite 文件时看到 EOFException 异常。以下是读取文件的代码。而且并不总是看到异常。考虑在 50K 个文件中出现 3 到 4 次。

public static byte[] decompressLzmaStream(InputStream inputStream, int size) 
    throws CompressorException, IOException {

    if(size < 1) {
        size = 1024 * 100;
    }

    try(LZMACompressorInputStream lzmaInputStream = 
                                           new LZMACompressorInputStream(inputStream);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(size)) {
        byte[] buffer = new byte[size];

        int length;
        while (-1 != (length = lzmaInputStream.read(buffer))) {
            byteArrayOutputStream.write(buffer, 0, length);
        }
        byteArrayOutputStream.flush();
        return byteArrayOutputStream.toByteArray();
    }
}

我正在使用以下依赖项进行解压

 <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-compress</artifactId>
    <version>1.20</version>
</dependency>

while (-1 != (length = lzmaInputStream.read(buffer))) { 行抛出异常。以下是例外。

java.io.EOFException: null at java.io.DataInputStream.readUnsignedByte(DataInputStream.java:290) 
at org.tukaani.xz.rangecoder.RangeDecoderFromStream.normalize(Unknown Source) 
at org.tukaani.xz.rangecoder.RangeDecoder.decodeBit(Unknown Source) 
at org.tukaani.xz.lzma.LZMADecoder.decode(Unknown Source) 
at org.tukaani.xz.LZMAInputStream.read(Unknown Source) 
at org.apache.commons.compress.compressors.lzma.
    LZMACompressorInputStream.read(LZMACompressorInputStream.java:62) 
at java.io.InputStream.read(InputStream.java:101)  

任何人都知道 commons-compress.

的以下构造函数
// I am using this constructor of LZMACompressorInputStream

public LZMACompressorInputStream(InputStream inputStream) throws IOException {
    this.in = new LZMAInputStream(this.countingStream = new CountingInputStream(inputStream), -1);
} 

// This is added in later version of commons-compress, what is memoryLimitInKb
public LZMACompressorInputStream(InputStream inputStream, int memoryLimitInKb) throws IOException {
    try {
        this.in = new LZMAInputStream(this.countingStream = new CountingInputStream(inputStream), memoryLimitInKb);
    } catch (MemoryLimitException var4) {
        throw new org.apache.commons.compress.MemoryLimitException((long)var4.getMemoryNeeded(), var4.getMemoryLimit(), var4);
    }
}

正如我所读对于 LZMA 流,我们需要将未压缩的大小传递给此处的构造函数 --> https://issues.apache.org/jira/browse/COMPRESS-286?page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel&focusedCommentId=14109417#comment-14109417

LZMA 解码器需要知道压缩流何时结束。如果在压缩期间已知未压缩的大小,则流的 header (位于流的开头) 将包含未压缩的大小。当解码器的输出达到这个大小时,解码器就知道 到达流的末尾。如果在压缩期间不知道未压缩的大小,则 header 将 不包含尺寸。在这种情况下,编码器假定流明确地终止于 流标记结束。

由于 LZMA 流也用于 7z 和 xz 等容器格式,LZMAOutputStream 并且 LZMAInputStream classes 还为 reading/writing 流提供构造器,而没有 header.

COMPRESS-286 是关于解压缩一个 7z 档案,其中包含一个使用 LZMA 压缩的条目。 7z 存档包含没有 header 的 LZMA 流。通常存储在 LZMA 的 header 中的信息与流分开存储。 用于读取 7z 档案的 Apache commons SevenZFile class 使用以下构造函数创建 LZMAInputStream objects:

LZMAInputStream(InputStream in, long uncompSize, byte propsByte, int dictSize)

构造函数的附加参数表示通常存储在LZMA流开头的header中的信息。 COMPRESS-286 的修复确保未压缩的大小(之前丢失)也被移交给 LZMAInputStream。

LZMACompressorInputStream 也使用 LZMAInputStream 但它假设压缩流包含显式 header。因此不可能通过它的构造函数传递信息。

memoryLimitInKb参数只限制解压使用的内存,与解压后的大小无关。所需内存的主要贡献者是所选字典的大小。此大小在压缩期间指定,并且也存储在流的 header 中。其最大值为 4 GB。通常字典的大小小于未压缩的大小。大于未压缩大小的字典绝对是内存浪费。损坏的 LZMA header 很容易导致 OOM 错误,并且被操纵的流甚至为拒绝服务攻击打开了大门。因此,当您读取未经验证的 LZMA 流时,限制最大内存使用量是明智的。

总结:由于您没有阅读带有 LZMA 压缩条目的 7z 存档,COMPRESS-286 与您的问题无关。但是类似的堆栈跟踪可能表明您的流的 header 有问题。

确保使用 LZACompressorOutputStream 的实例压缩数据(自动选择 字典大小,所有其他参数并确保写入 header)。如果您应该直接使用 LZAOutputStream,请确保您使用的实例实际写入 header.