Appendix-Data-Compression

附录:数据压缩

Java I/O类库提供了可以读写压缩格式流的类。你可以将其他I/O类包装起来用于提供压缩功能。

这些类不是从ReaderWriter类派生的,而是InputStreamOutputStream层级结构的一部分。这是由于压缩库处理的是字节,而不是字符。但是,你可能会被迫混合使用两种类型的流(请记住,你可以使用InputStreamReaderOutputStreamWriter,这两个类可以在字节类型和字符类型之间轻松转换

压缩类 功能
CheckedInputStream getCheckSum() 可以对任意InputStream计算校验和(而不只是解压)
CheckedOutputStream getCheckSum() 可以对任意OutputStream计算校验和(而不只是压缩)
DeflaterOutputStream 压缩类的基类
ZipOutputStream DeflaterOutputStream类的一种,用于压缩数据到Zip文件结构
GZIPOutputStream DeflaterOutputStream类的一种,用于压缩数据到GZIP文件结构
InflaterInputStream 解压类的基类
ZipInputStream InflaterInputStream类的一种,用于解压Zip文件结构的数据
GZIPInputStream InflaterInputStream类的一种,用于解压GZIP文件结构的数据

尽管存在很多压缩算法,但是ZipGZIP可能是最常见的。你可以使用许多用于读取和写入这些格式的工具,来轻松操作压缩数据。

使用Gzip简单压缩

GZIP接口十分简单,因此当你有一个需要压缩的数据流(而不是一个包含不同数据分片的容器)时,使用GZIP更为合适。如下是一个压缩单个文件的示例:

// compression/GZIPcompress.java
// (c)2017 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
// {java GZIPcompress GZIPcompress.java}
// {VisuallyInspectOutput}

public class GZIPcompress {
    public static void main(String[] args) {
        if (args.length == 0) {
            System.out.println(
                    "Usage: \nGZIPcompress file\n" +
                            "\tUses GZIP compression to compress " +
                            "the file to test.gz");
            System.exit(1);
        }
        try (
                InputStream in = new BufferedInputStream(
                        new FileInputStream(args[0]));
                BufferedOutputStream out =
                        new BufferedOutputStream(
                                new GZIPOutputStream(
                                        new FileOutputStream("test.gz")))
        ) {
            System.out.println("Writing file");
            int c;
            while ((c = in.read()) != -1)
                out.write(c);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        System.out.println("Reading file");
        try (
                BufferedReader in2 = new BufferedReader(
                        new InputStreamReader(new GZIPInputStream(
                                new FileInputStream("test.gz"))))
        ) {
            in2.lines().forEach(System.out::println);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

使用压缩类非常简单,你只需要把你的输出流包装在GZIPOutputStreamZipOutputStream中,将输入流包装在GZIPInputStreamZipInputStream。其他的一切就只是普通的I/O读写。这是面向字符流和面向字节流的混合示例;in使用Reader类,而GZIPOutputStreams构造函数只能接受OutputStream对象,而不能接受Writer对象。当打开文件的时候,GZIPInputStream会转换成为Reader

使用zip多文件存储

支持Zip格式的库比GZIP库更广泛。有了它,你可以轻松存储多个文件,甚至还有一个单独的类可以轻松地读取Zip文件。该库使用标准Zip格式,因此它可以与当前可在Internet上下载的所有Zip工具无缝协作。以下示例与前一个示例具有相同的形式,但它可以根据需要处理任意数量的命令行参数。此外,它还显示了Checksum类计算和验证文件的校验和。有两种校验和类型:Adler32(更快)和CRC32(更慢但更准确

// compression/ZipCompress.java
// (c)2017 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
// Uses Zip compression to compress any
// number of files given on the command line
// {java ZipCompress ZipCompress.java}
// {VisuallyInspectOutput}
public class ZipCompress {
    public static void main(String[] args) {
        try (
                FileOutputStream f =
                        new FileOutputStream("test.zip");
                CheckedOutputStream csum =
                        new CheckedOutputStream(f, new Adler32());
                ZipOutputStream zos = new ZipOutputStream(csum);
                BufferedOutputStream out =
                        new BufferedOutputStream(zos)
        ) {
            zos.setComment("A test of Java Zipping");
            // No corresponding getComment(), though.
            for (String arg : args) {
                System.out.println("Writing file " + arg);
                try (
                        InputStream in = new BufferedInputStream(
                                new FileInputStream(arg))
                ) {
                    zos.putNextEntry(new ZipEntry(arg));
                    int c;
                    while ((c = in.read()) != -1)
                        out.write(c);
                }
                out.flush();
            }
            // Checksum valid only after the file is closed!
            System.out.println(
                    "Checksum: " + csum.getChecksum().getValue());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        // Now extract the files:
        System.out.println("Reading file");
        try (
                FileInputStream fi =
                        new FileInputStream("test.zip");
                CheckedInputStream csumi =
                        new CheckedInputStream(fi, new Adler32());
                ZipInputStream in2 = new ZipInputStream(csumi);
                BufferedInputStream bis =
                        new BufferedInputStream(in2)
        ) {
            ZipEntry ze;
            while ((ze = in2.getNextEntry()) != null) {
                System.out.println("Reading file " + ze);
                int x;
                while ((x = bis.read()) != -1)
                    System.out.write(x);
            }
            if (args.length == 1)
                System.out.println(
                        "Checksum: " + csumi.getChecksum().getValue());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        // Alternative way to open and read Zip files:
        try (
                ZipFile zf = new ZipFile("test.zip")
        ) {
            Enumeration e = zf.entries();
            while (e.hasMoreElements()) {
                ZipEntry ze2 = (ZipEntry) e.nextElement();
                System.out.println("File: " + ze2);
                // ... and extract the data as before
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

对于要添加到存档的每个文件,必须调用 putNextEntry() 并传递ZipEntry对象。 ZipEntry对象包含一个扩展接口,用于获取和设置Zip文件中该特定条目的所有可用数据:名称,压缩和未压缩大小,日期,CRC校验和,额外字段数据,注释,压缩方法以及它是否是目录条目。但是,即使Zip格式有设置密码的方法,JavaZip库也不支持。虽然CheckedInputStreamCheckedOutputStream都支持Adler32CRC32校验和,但ZipEntry类仅支持CRC接口。这是对基础Zip格式的限制,但它可能会限制你使用更快的Adler32

要提取文件,ZipInputStream有一个 getNextEntry() 方法,这个方法在有文件存在的情况下调用,会返回下一个ZipEntry。作为一个更简洁的替代方法,你可以使用ZipFile对象读取该文件,该对象具有方法entries()返回一个包裹ZipEntriesEnumeration

要读取校验和,你必须以某种方式访问关联的Checksum对象。这里保留了对CheckedOutputStreamCheckedInputStream对象的引用,但你也可以保持对Checksum对象的引用。 Zip流中的一个令人困惑的方法是 setComment()。如ZipCompress所示。在Java中,你可以在编写文件时设置注释,但是没有办法恢复ZipInputStream中的注释。注释似乎仅通过ZipEntry在逐个条目的基础上完全支持。

使用GZIPZip库时,你不仅被限制于文件——你可以压缩任何内容,包括通过网络连接发送的数据。

Javajar

Zip格式也用于JAR(Java ARchive)文件格式,这是一种将一组文件收集到单个压缩文件中的方法,就像Zip一样。但是,与Java中的其他所有内容一样,JAR文件是跨平台的,因此你不必担心平台问题。你还可以将音频和图像文件像类文件一样包含在其中。

JAR文件由一个包含压缩文件集合的文件和一个描述它们的“清单(manifest)”组成(你可以创建自己的清单文件;否则,jar程序将为你执行此操作)你可以在JDK文档中,找到更多关于JAR清单的信息。

JDK附带的jar工具会自动压缩你选择的文件。你可以在命令行上调用它:

jar [options] destination [manifest] inputfile(s)

选项是一组字母(不需要连字符或任何其他指示符Unix / Linux用户会注意到这些选项与tar命令选项的相似性。这些是:

选项 功能
c 创建一个新的或者空的归档文件
t 列出内容目录
x 提取所有文件
x file 提取指定的文件
f 这代表着“传递文件的名称”如果你不使用它,jar假定它的输入将来自标准输入,或者,如果它正在创建一个文件,它的输出将转到标准输出。
m 代表第一个参数是用户创建的清单文件的名称。
v 生成详细的输出用于表述jar所作的事情
0 仅存储文件;不压缩文件(用于创建放在类路径中的JAR文件
M 不要自动创建清单文件

如果放入JAR文件的文件中包含子目录,则会自动添加该子目录,包括其所有子目录等。还会保留路径信息。

以下是一些调用jar的典型方法。以下命令创建名为myJarFileJAR文件。 jar包含当前目录中的所有类文件,以及自动生成的清单文件:

jar cf myJarFile.jar *.class

下一个命令与前面的示例类似,但它添加了一个名为myManifestFile.mf的用户创建的清单文件。 :

jar cmf myJarFile.jar myManifestFile.mf *.class

这个命令输出了myJarFile.jar中的文件目录:

jar tf myJarFile.jar

如下添加了一个“verbose”的标志,用于生成更多关于myJarFile.jar中文件的详细信息:

jar tvf myJarFile.jar

假设audioclassesimage都是子目录,它将所有子目录组合到文件myApp.jar中。还包括“verbose”标志,以便在jar程序工作时提供额外的反馈:

jar cvf myApp.jar audio classes image

如果你在创建JAR文件时使用了0(零) 选项,该文件将会被替换在你的类路径(CLASSPATH)中:

CLASSPATH="lib1.jar;lib2.jar;"

然后Java可以搜索到lib1.jarlib2.jar的类文件。

jar工具不像Zip实用程序那样通用。例如,你无法将文件添加或更新到现有JAR文件;只能从头开始创建JAR文件。

此外,你无法将文件移动到JAR文件中,在移动文件时将其删除。

但是,在一个平台上创建的JAR文件可以通过任何其他平台上的jar工具透明地读取(这个问题有时会困扰Zip实用程序

上一页
下一页