数据压缩

Java I/O 库包含了以压缩格式读取和写人流的类。可以将它们包装在其他 I/O 类中,提供压缩功能。

这些类是 InputStream 和 OutputStream 继承层次结构的一部分,而没有继承 Reader 和 Writer 类,这是因为压缩库使用的是字节,而不是字符。但是,有时不得不混合使用这两种类型的流。(注意,你可以使用 InputStreamReader 和 OutputStreamWriter 在两种类型之间轻松转换)下表介绍了压缩类及其功能:

压缩类
功能

CheckedInputStream

getCheckSum() 为任意 InputStream (不仅包括解压缩的流) 生成校验和

CheckedOutputStream

getCheckSum() 为任意 OutputStream (不仅包括压缩过的流) 生成校验和

DeflaterOutputStream

压缩类的基类

ZipOutputStream

将数据压缩为 Zip 文件格式的 DeflaterOutputStream

GZIPOutputStream

将数据压缩为 GZIP 文件格式的 DeflaterOutputStream

InflaterInputStream

解压缩类的基类

ZipInputStream

继于 InflaterInputStream,用于解压缩以 Zip 文件格式存储的数据

GZIPInputStream

继于 InflaterInputStream,用于解压缩以 GZIP 文件格式存储的数据

尽管有很多压缩算法,但 Zip 和 GZIP 可能是最常见的。Java 里有许多可用于读取和写人这些格式的工具,使用它们就能很轻松地处理压缩数据。

使用 GZIP 进行简单压缩

GZIP 接口很简单,因此要压缩单个数据流(而不是不同数据块的容器)时,使用它可能更合适。下面示例会压缩单个文件:

public class GZIPcompress {
    public static void main(String[] args) {
        if(args.length == 0) {
            System.out.println("""
                    Usage:\s
                    GZIPcompress file
                    \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/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/test.gz"))))) {
            in2.lines().forEach(System.out::println);
        }catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

使用压缩类很简单:将输出流包装在 GIPOutputStream 或 ZipOutputstream 中,并将输人流包装在 GZIPInputStream 或 ZipInputStream 中,其他都是普通的 I/O 读写。这是一个混合使用字符流和字节流的例子,in 使用了 Reader 类,GZIPOutputStream 的构造器只能接受一个 OutputStream 对象,而不是一个 Writer 对象。当文件被打开时,GZIPInputStream 被转换为一个 Reader。

使用 Zip 进行多文件存储

Java 库对 Zip 格式的支持力度比 GZIP 更大,你可以通过 Java 库轻松地存储多个文件,它甚至还提供了一个单独的类,来帮助我们更容易地读取 Zip 文件。该库使用标准的 Zip 格式,因此可以与当前网上下载的所有 Zip 工具无缝合作。下面的示例与前面的示例形式相同,但它可以根据需要处理任意数量的命令行参数。此外,他还使用 Checksum 类来计算和验证文件的校验和,有两种 Checksum 类型:Adler32(更快)和 CRC32(更慢但准确)。

对于要添加到存档中的每个文件,都必须调用 putNextEntry() 并传递给它一个 ZipEntry 对象。ZipEntry 对象提供了功能丰富的接口,用于获取和设置 Zip 文件中该特定条目的所有可用数据:名称、压缩和未压缩大小、日期、CRC校验和、额外字段数据注释、压缩方法,以及它是否是一个目录条目。不过,虽然 Zip 格式允许设置密码,但 Java 的 Zip 库并不支持这一功能。尽管 CheckedInputStream 和 CheckedOutputStream 支持 Adler32 和 CRC32 校验和,但 ZipEntry 类只支持 CRC 的接口。这是底层 Zip 格式的一个限制。

为了提取文件,ZipInputStream 有一个 getNextEntry() 方法,如果有数据则返回下一个ZipEntry。作为更简洁的替代方法,你可以使用 ZipFile 对象来读取文件,该对象提供了 entries() 方法,可以返回一个 Enumeration 给 ZipEntries。

如果想要读取校验和,必须以某种方式访问关联的 Checksum 对象。本例中我们保留了 CheckedOutputStream 和 CheckedInputStream 对象的引用,但也可以只保留对 Checksum 对象的引用。

Zip 流中有一个令人困惑的方法 setComment()。如 ZipCompress.java 所示,你可以在写文件的时候设置注释,但在 ZipInputStream 中没有办法恢复这个注释,似乎只能通过 ZipEntry 在每一项上设置注释。

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

Java 档案(Jars)

JAR 文件格式里也使用了 Zip 格式,它是一种将一组文件收集到单个压缩文件的方法,就像 Zip 一样。JAR 文件是跨平台的,并且还可以包含音频和图像文件,以及类文件。

JAR 文件由单个文件组成,该文件包含了一组压缩文件以及描述它们的“清单文件”(manifest)。(可以创建自己的清单文件,否则 jar 程序会自动为你创建)可以在 JDK 文档中找到有关 JAR 清单的更多信息。

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

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

字母
效果

c

创建一个新的或空的存档

t

列出目录

x

提取所有文件

x file

提取指定文件

f

它表示 “我将给你指定文件名”。如果不使用这个选项,jar 会假定输入来自标准输入;或者,如果正在创建一个文件,它的输出将转到标准输出

m

表示第一个参数是用户创建的清单文件的名称

v

生成详细的输出来描述 jar 正在做什么

0

只存储文件,不压缩文件(用于创建放在类路径中的 JAR 文件)

H

禁止自动创建清单文件

如果放入 JAR 文件的内容包含子目录,那么它会自动添加该子目录,包括该子目录的子目录,以此类推。同时路径信息也会被保留下来。

然后介绍调用 jar 的一些典型方式。下面这个命令创建了一个名为 myJarFile.jar 的 JAR 文件,其中包含当前目录下的所有类文件,以及一个自动生成的清单文件:

下一个命令与上面相似,但添加了一个名为 myManifestFile.mf 的用户创建的清单文件:

下面的命令会打印 myJarFile.jar 中文件的目录列表:

添加一个“详细模式”的标志,以提供 myJarFile.jar 中文件的更多信息:

假设 audio、classes 和 image 是子目录,下面的命令将所有子目录合并到文件 myApp.jar 中。它还提供了一个“详细模式”的标志,用来在jar程序运行时提供额外的反馈:

如果使用了 0(零)选项来创建 JAR 文件,则生成的文件可以放在你的 CLASSPATH 下:

然后 Java 可以搜索 lib1.jar 和 lib2.jar 来查找类文件。

jar 工具不像 Zip 工具那样通用。例如,不能向现有的 JAR 文件添加或更新文件,只能从头开始创建 JAR 文件。此外,无法将文件移动到 JAR 文件中,也不能在移出时将其删除。但是,在一个平台上创建的 JAR 文件可以在任何其他平台上用 jar 工具毫无阻得地读取。(Zip 工具有时会遇到麻烦)