Zlib压缩:在Java中使用Deflate和Inflate类

13

我想使用java.util.zip中的Deflate和Inflate类进行zlib压缩。

我已经成功地使用Deflate进行了代码压缩,但是在解压缩时,我遇到了以下错误-

Exception in thread "main" java.util.zip.DataFormatException: unknown compression method
    at java.util.zip.Inflater.inflateBytes(Native Method)
    at java.util.zip.Inflater.inflate(Inflater.java:238)
    at java.util.zip.Inflater.inflate(Inflater.java:256)
    at zlibCompression.main(zlibCompression.java:53)

以下是我的代码 -

import java.util.zip.*;
import java.io.*;

public class zlibCompression {

    /**
     * @param args
     */
    public static void main(String[] args) throws IOException, DataFormatException {
        // TODO Auto-generated method stub

        String fname = "book1";
        FileReader infile = new FileReader(fname);
        BufferedReader in = new BufferedReader(infile);

        FileOutputStream out = new FileOutputStream("book1out.dfl");
        //BufferedInputStream in = new BufferedInputStream(new FileInputStream(filename));

        Deflater compress = new Deflater();
        Inflater decompress = new Inflater();

        String readFile = in.readLine();
        byte[] bx = readFile.getBytes();

        while(readFile!=null){
            byte[] input = readFile.getBytes();
            byte[] compressedData = new byte[1024];
            compress.setInput(input);
            compress.finish();
            int compressLength = compress.deflate(compressedData, 0, compressedData.length);
            //System.out.println(compressedData);
            out.write(compressedData, 0, compressLength);
            readFile = in.readLine();
        }

        File abc = new File("book1out.dfl");
        InputStream is = new FileInputStream("book1out.dfl");

        InflaterInputStream infl = new InflaterInputStream(new FileInputStream("book1out.dfl"), new Inflater());
        FileOutputStream outFile = new FileOutputStream("decompressed.txt");

        byte[] b = new byte[1024];
        while(true){

            int a = infl.read(b,0,1024);
            if(a==0)
                break;

            decompress.setInput(b);
            byte[] fresult = new byte[1024];
            //decompress.in
            int resLength = decompress.inflate(fresult);
            //outFile.write(b,0,1);
            //String outt = new String(fresult, 0, resLength);
            //System.out.println(outt);
        }

        System.out.println("complete");

    }
}

这是一份作业吗?一个错误是过早调用finish,另一个错误是在没有长度的情况下使用setInput,还有一个错误是没有检查deflate过程是否返回了超过1024个数据。 - bestsss
2个回答

31
你在这里想要做什么?你使用了一个InflaterInputStream,它解压缩了你的数据,然后你又试图将这个已经解压缩的数据再次传递给一个Inflater吗?使用其中一个即可,但不要同时使用两者。
这是导致你异常的原因。
此外,还有一些次要错误,比如bestsss提到的:
- 你在循环中完成了压缩-完成后,就无法再添加任何数据了。 - 你没有检查deflate过程产生了多少输出。如果您有长行,则可能超过1024字节。 - 您将输入设置为Inflater而未设置长度a。
我还发现了一些其他问题:
- 写入后(在从同一文件中读取之前),你没有关闭你的FileOutputStream。 - 你使用readLine()读取一行文本,但然后你没有再添加换行符,这意味着你的解压缩文件中不会有任何换行符。 - 你无需进行字节转换以及反向转换。 - 你创建了一些没用过的变量。
我不会试图修正你的程序。这里是一个简单的程序,它使用DeflaterOutputStream和InflaterInputStream来实现你想要的功能。(你也可以使用JZlib的ZInputStream和ZOutputStream代替。)
import java.util.zip.*;
import java.io.*;

/**
 * Example program to demonstrate how to use zlib compression with
 * Java.
 * Inspired by https://dev59.com/dm025IYBdhLWcg3wKSmP.
 */
public class ZlibCompression {

    /**
     * Compresses a file with zlib compression.
     */
    public static void compressFile(File raw, File compressed)
        throws IOException
    {
        InputStream in = new FileInputStream(raw);
        OutputStream out =
            new DeflaterOutputStream(new FileOutputStream(compressed));
        shovelInToOut(in, out);
        in.close();
        out.close();
    }

    /**
     * Decompresses a zlib compressed file.
     */
    public static void decompressFile(File compressed, File raw)
        throws IOException
    {
        InputStream in =
            new InflaterInputStream(new FileInputStream(compressed));
        OutputStream out = new FileOutputStream(raw);
        shovelInToOut(in, out);
        in.close();
        out.close();
    }

    /**
     * Shovels all data from an input stream to an output stream.
     */
    private static void shovelInToOut(InputStream in, OutputStream out)
        throws IOException
    {
        byte[] buffer = new byte[1000];
        int len;
        while((len = in.read(buffer)) > 0) {
            out.write(buffer, 0, len);
        }
    }


    /**
     * Main method to test it all.
     */
    public static void main(String[] args) throws IOException, DataFormatException {
        File compressed = new File("book1out.dfl");
        compressFile(new File("book1"), compressed);
        decompressFile(compressed, new File("decompressed.txt"));
    }
}

为了提高效率,使用缓冲流包装文件流可能非常有用。如果这对性能至关重要,请进行度量。


你确定这是针对zlib的吗?我尝试解压缩一个zlib文件,但是出现了“未知压缩方法”的错误。如果我查看http://docs.oracle.com/javase/1.4.2/docs/api/java/util/zip/package-summary.html,那么我会看到`Inflater`提到了zlib,但是你使用的是`InflaterInputStream`,它提到了deflate压缩方法。 这是文件: https://dl.dropboxusercontent.com/u/17630770/temp/pc_oj_simple_AnimTake1_11.icecache.zip - clankill3r
InflaterInputStream使用(如果使用没有Inflater参数的构造函数)一个new Inflater()。而这个构造函数使用nowrap = false,这意味着zlib压缩。(我没有检查你的文件。) - Paŭlo Ebermann

4

Paŭlo Ebermann的代码可以通过使用try-with-resources进一步改进:

import java.util.Scanner;
import java.util.zip.*;
import java.io.*;

public class ZLibCompression
{
    public static void compress(File raw, File compressed) throws IOException
    {
        try (InputStream inputStream = new FileInputStream(raw);
             OutputStream outputStream = new DeflaterOutputStream(new FileOutputStream(compressed)))
        {
            copy(inputStream, outputStream);
        }
    }

    public static void decompress(File compressed, File raw)
            throws IOException
    {
        try (InputStream inputStream = new InflaterInputStream(new FileInputStream(compressed));
             OutputStream outputStream = new FileOutputStream(raw))
        {
            copy(inputStream, outputStream);
        }
    }

    public static String decompress(File compressed) throws IOException
    {
        try (InputStream inputStream = new InflaterInputStream(new FileInputStream(compressed)))
        {
            return toString(inputStream);
        }
    }

    private static String toString(InputStream inputStream)
    {
        try (Scanner scanner = new Scanner(inputStream).useDelimiter("\\A"))
        {
            return scanner.hasNext() ? scanner.next() : "";
        }
    }

    private static void copy(InputStream inputStream, OutputStream outputStream)
            throws IOException
    {
        byte[] buffer = new byte[1000];
        int length;

        while ((length = inputStream.read(buffer)) > 0)
        {
            outputStream.write(buffer, 0, length);
        }
    }
}

另一种使用 Zlib 的方法是使用 InflaterDeflater 类:

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;

import static java.util.zip.Deflater.BEST_COMPRESSION;

public class CompressionUtils
{
    private static final int BUFFER_SIZE = 1024;

    public static byte[] compress(final byte[] data) throws IOException
    {
        final Deflater deflater = new Deflater();
        deflater.setLevel(BEST_COMPRESSION);
        deflater.setInput(data);

        try (final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length))
        {
            deflater.finish();
            final byte[] buffer = new byte[BUFFER_SIZE];
            while (!deflater.finished())
            {
                final int count = deflater.deflate(buffer);
                outputStream.write(buffer, 0, count);
            }

            return outputStream.toByteArray();
        }
    }

    public static byte[] decompress(final byte[] data) throws Exception
    {
        final Inflater inflater = new Inflater();
        inflater.setInput(data);

        try (final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length))
        {
            byte[] buffer = new byte[BUFFER_SIZE];
            while (!inflater.finished())
            {
                final int count = inflater.inflate(buffer);
                outputStream.write(buffer, 0, count);
            }

            return outputStream.toByteArray();
        }
    }
}

扫描器的目标是什么?只是获取流的第一行吗? - Paŭlo Ebermann
1
@Paŭlo Ebermann: 这是一种一次性读取整个流的技巧。 - BullyWiiPlaza
为什么Buffered(In|Out)putStream不存在? - Stephan
1
@Stephan:没有特别的原因,这段代码运行良好。不过对于更大的文件,包含缓冲版本可能会更快。 - BullyWiiPlaza

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