|
3 | 3 | <!-- Appendix: Data Compression -->
|
4 | 4 | # 附录:数据压缩
|
5 | 5 |
|
| 6 | +Java I/O 类库提供了可以读写压缩格式流的类。你可以将其他 I/O 类包装起来用于提供压缩功能。 |
| 7 | + |
| 8 | +这些类不是从 **Reader** 和 **Writer** 类派生的,而是 **InputStream** 和 **OutputStream** 层级结构的一部分。这是由于压缩库处理的是字节,而不是字符。但是,你可能会被迫混合使用两种类型的流(请记住,你可以使用 **InputStreamReader** 和 **OutputStreamWriter**,这两个类可以在字节类型和字符类型之间轻松转换)。 |
| 9 | + |
| 10 | +| 压缩类 | 功能 | |
| 11 | +| ------------------------ | ------------------------------------------------------------ | |
| 12 | +| **CheckedInputStream** | `getCheckSum()` 可以对任意 **InputStream** 计算校验和(而不只是解压) | |
| 13 | +| **CheckedOutputStream** | `getCheckSum()` 可以对任意 **OutputStream** 计算校验和(而不只是压缩) | |
| 14 | +| **DeflaterOutputStream** | 压缩类的基类 | |
| 15 | +| **ZipOutputStream** | **DeflaterOutputStream** 类的一种,用于压缩数据到 Zip 文件结构 | |
| 16 | +| **GZIPOutputStream** | **DeflaterOutputStream** 类的一种,用于压缩数据到 GZIP 文件结构 | |
| 17 | +| **InflaterInputStream** | 解压类的基类 | |
| 18 | +| **ZipInputStream** | **InflaterInputStream** 类的一种,用于解压 Zip 文件结构的数据 | |
| 19 | +| **GZIPInputStream** | **InflaterInputStream** 类的一种,用于解压 GZIP 文件结构的数据 | |
| 20 | + |
| 21 | +尽管存在很多压缩算法,但是 Zip 和 GZIP 可能是最常见的。你可以使用许多用于读取和写入这些格式的工具,来轻松操作压缩数据。 |
6 | 22 |
|
7 | 23 | <!-- Simple Compression with GZIP -->
|
8 |
| -## 使用Gzip简单压缩 |
9 | 24 |
|
| 25 | +## 使用 Gzip 简单压缩 |
10 | 26 |
|
11 | 27 | <!-- Multifile Storage with Zip -->
|
12 |
| -## 使用zip多文件存储 |
| 28 | + |
| 29 | +GZIP 接口十分简单,因此当你有一个需要压缩的数据流(而不是一个包含不同数据分片的容器)时,使用 GZIP 更为合适。如下是一个压缩单个文件的示例: |
| 30 | + |
| 31 | +```java |
| 32 | +// compression/GZIPcompress.java |
| 33 | +// (c)2017 MindView LLC: see Copyright.txt |
| 34 | +// We make no guarantees that this code is fit for any purpose. |
| 35 | +// Visit http://OnJava8.com for more book information. |
| 36 | +// {java GZIPcompress GZIPcompress.java} |
| 37 | +// {VisuallyInspectOutput} |
| 38 | + |
| 39 | +public class GZIPcompress { |
| 40 | + public static void main(String[] args) { |
| 41 | + if (args.length == 0) { |
| 42 | + System.out.println( |
| 43 | + "Usage: \nGZIPcompress file\n" + |
| 44 | + "\tUses GZIP compression to compress " + |
| 45 | + "the file to test.gz"); |
| 46 | + System.exit(1); |
| 47 | + } |
| 48 | + try ( |
| 49 | + InputStream in = new BufferedInputStream( |
| 50 | + new FileInputStream(args[0])); |
| 51 | + BufferedOutputStream out = |
| 52 | + new BufferedOutputStream( |
| 53 | + new GZIPOutputStream( |
| 54 | + new FileOutputStream("test.gz"))) |
| 55 | + ) { |
| 56 | + System.out.println("Writing file"); |
| 57 | + int c; |
| 58 | + while ((c = in.read()) != -1) |
| 59 | + out.write(c); |
| 60 | + } catch (IOException e) { |
| 61 | + throw new RuntimeException(e); |
| 62 | + } |
| 63 | + System.out.println("Reading file"); |
| 64 | + try ( |
| 65 | + BufferedReader in2 = new BufferedReader( |
| 66 | + new InputStreamReader(new GZIPInputStream( |
| 67 | + new FileInputStream("test.gz")))) |
| 68 | + ) { |
| 69 | + in2.lines().forEach(System.out::println); |
| 70 | + } catch (IOException e) { |
| 71 | + throw new RuntimeException(e); |
| 72 | + } |
| 73 | + } |
| 74 | +} |
| 75 | + |
| 76 | +``` |
| 77 | + |
| 78 | +使用压缩类非常简单,你只需要把你的输出流包装在 **GZIPOutputStream** 或 **ZipOutputStream** 中,将输入流包装在 **GZIPInputStream** 或 **ZipInputStream**。其他的一切就只是普通的 I/O 读写。这是面向字符流和面向字节流的混合示例;in 使用 Reader 类,而 **GZIPOutputStreams** 构造函数只能接受 **OutputStream** 对象,而不能接受 **Writer** 对象。当打开文件的时候,**GZIPInputStream** 会转换成为 **Reader**。 |
| 79 | + |
| 80 | +## 使用 zip 多文件存储 |
| 81 | + |
| 82 | +支持 Zip 格式的库比 GZIP 库更广泛。有了它,你可以轻松存储多个文件,甚至还有一个单独的类可以轻松地读取 Zip 文件。该库使用标准 Zip 格式,因此它可以与当前可在 Internet 上下载的所有 Zip 工具无缝协作。以下示例与前一个示例具有相同的形式,但它可以根据需要处理任意数量的命令行参数。此外,它还显示了 **Checksum** 类计算和验证文件的校验和。有两种校验和类型:Adler32(更快)和 CRC32(更慢但更准确)。 |
| 83 | + |
| 84 | +```java |
| 85 | +// compression/ZipCompress.java |
| 86 | +// (c)2017 MindView LLC: see Copyright.txt |
| 87 | +// We make no guarantees that this code is fit for any purpose. |
| 88 | +// Visit http://OnJava8.com for more book information. |
| 89 | +// Uses Zip compression to compress any |
| 90 | +// number of files given on the command line |
| 91 | +// {java ZipCompress ZipCompress.java} |
| 92 | +// {VisuallyInspectOutput} |
| 93 | +public class ZipCompress { |
| 94 | + public static void main(String[] args) { |
| 95 | + try ( |
| 96 | + FileOutputStream f = |
| 97 | + new FileOutputStream("test.zip"); |
| 98 | + CheckedOutputStream csum = |
| 99 | + new CheckedOutputStream(f, new Adler32()); |
| 100 | + ZipOutputStream zos = new ZipOutputStream(csum); |
| 101 | + BufferedOutputStream out = |
| 102 | + new BufferedOutputStream(zos) |
| 103 | + ) { |
| 104 | + zos.setComment("A test of Java Zipping"); |
| 105 | + // No corresponding getComment(), though. |
| 106 | + for (String arg : args) { |
| 107 | + System.out.println("Writing file " + arg); |
| 108 | + try ( |
| 109 | + InputStream in = new BufferedInputStream( |
| 110 | + new FileInputStream(arg)) |
| 111 | + ) { |
| 112 | + zos.putNextEntry(new ZipEntry(arg)); |
| 113 | + int c; |
| 114 | + while ((c = in.read()) != -1) |
| 115 | + out.write(c); |
| 116 | + } |
| 117 | + out.flush(); |
| 118 | + } |
| 119 | + // Checksum valid only after the file is closed! |
| 120 | + System.out.println( |
| 121 | + "Checksum: " + csum.getChecksum().getValue()); |
| 122 | + } catch (IOException e) { |
| 123 | + throw new RuntimeException(e); |
| 124 | + } |
| 125 | + // Now extract the files: |
| 126 | + System.out.println("Reading file"); |
| 127 | + try ( |
| 128 | + FileInputStream fi = |
| 129 | + new FileInputStream("test.zip"); |
| 130 | + CheckedInputStream csumi = |
| 131 | + new CheckedInputStream(fi, new Adler32()); |
| 132 | + ZipInputStream in2 = new ZipInputStream(csumi); |
| 133 | + BufferedInputStream bis = |
| 134 | + new BufferedInputStream(in2) |
| 135 | + ) { |
| 136 | + ZipEntry ze; |
| 137 | + while ((ze = in2.getNextEntry()) != null) { |
| 138 | + System.out.println("Reading file " + ze); |
| 139 | + int x; |
| 140 | + while ((x = bis.read()) != -1) |
| 141 | + System.out.write(x); |
| 142 | + } |
| 143 | + if (args.length == 1) |
| 144 | + System.out.println( |
| 145 | + "Checksum: " + csumi.getChecksum().getValue()); |
| 146 | + } catch (IOException e) { |
| 147 | + throw new RuntimeException(e); |
| 148 | + } |
| 149 | + // Alternative way to open and read Zip files: |
| 150 | + try ( |
| 151 | + ZipFile zf = new ZipFile("test.zip") |
| 152 | + ) { |
| 153 | + Enumeration e = zf.entries(); |
| 154 | + while (e.hasMoreElements()) { |
| 155 | + ZipEntry ze2 = (ZipEntry) e.nextElement(); |
| 156 | + System.out.println("File: " + ze2); |
| 157 | + // ... and extract the data as before |
| 158 | + } |
| 159 | + } catch (IOException e) { |
| 160 | + throw new RuntimeException(e); |
| 161 | + } |
| 162 | + } |
| 163 | +} |
| 164 | +``` |
| 165 | + |
| 166 | +对于要添加到存档的每个文件,必须调用 `putNextEntry()` 并传递 **ZipEntry** 对象。 **ZipEntry** 对象包含一个扩展接口,用于获取和设置 Zip 文件中该特定条目的所有可用数据:名称,压缩和未压缩大小,日期,CRC 校验和,额外字段数据,注释,压缩方法以及它是否是目录条目。但是,即使 Zip 格式有设置密码的方法,Java 的 Zip 库也不支持。虽然 **CheckedInputStream** 和 **CheckedOutputStream** 都支持 Adler32 和 CRC32 校验和,但 **ZipEntry** 类仅支持 CRC 接口。这是对基础 Zip 格式的限制,但它可能会限制你使用更快的 Adler32。 |
| 167 | + |
| 168 | +要提取文件,**ZipInputStream** 有一个 `getNextEntry()` 方法,这个方法在有文件存在的情况下调用,会返回下一个 **ZipEntry**。作为一个更简洁的替代方法,你可以使用 **ZipFile** 对象读取该文件,该对象具有方法 entries() 返回一个包裹 **ZipEntries** 的 **Enumeration**。 |
| 169 | + |
| 170 | +要读取校验和,你必须以某种方式访问关联的 **Checksum** 对象。这里保留了对 **CheckedOutputStream** 和 **CheckedInputStream** 对象的引用,但你也可以保持对 **Checksum** 对象的引用。 Zip 流中的一个令人困惑的方法是 `setComment()`。如 **ZipCompress** 所示。在 Java 中,你可以在编写文件时设置注释,但是没有办法恢复 **ZipInputStream** 中的注释。注释似乎仅通过 **ZipEntry** 在逐个条目的基础上完全支持。 |
| 171 | + |
| 172 | +使用 GZIP 或 Zip 库时,你不仅被限制于文件——你可以压缩任何内容,包括通过网络连接发送的数据。 |
| 173 | + |
13 | 174 |
|
14 | 175 |
|
15 | 176 | <!-- Java Archives (Jars) -->
|
16 |
| -## Java的jar |
| 177 | + |
| 178 | +## Java 的 jar |
| 179 | + |
| 180 | +Zip 格式也用于 JAR(Java ARchive)文件格式,这是一种将一组文件收集到单个压缩文件中的方法,就像 Zip 一样。但是,与 Java 中的其他所有内容一样,JAR 文件是跨平台的,因此你不必担心平台问题。你还可以将音频和图像文件像类文件一样包含在其中。 |
| 181 | + |
| 182 | +JAR 文件由一个包含压缩文件集合的文件和一个描述它们的“清单(manifest)”组成。(你可以创建自己的清单文件;否则,jar 程序将为你执行此操作。)你可以在 JDK 文档中,找到更多关于 JAR 清单的信息。 |
| 183 | + |
| 184 | +JDK 附带的 jar 工具会自动压缩你选择的文件。你可以在命令行上调用它: |
| 185 | + |
| 186 | +```shell |
| 187 | +jar [options] destination [manifest] inputfile(s) |
| 188 | +``` |
| 189 | + |
| 190 | +选项是一组字母(不需要连字符或任何其他指示符)。 Unix / Linux 用户会注意到这些选项与 tar 命令选项的相似性。这些是: |
| 191 | + |
| 192 | +| 选项 | 功能 | |
| 193 | +| ---------- | ------------------------------------------------------------ | |
| 194 | +| **c** | 创建一个新的或者空的归档文件 | |
| 195 | +| **t** | 列出内容目录 | |
| 196 | +| **x** | 提取所有文件 | |
| 197 | +| **x** file | 提取指定的文件 | |
| 198 | +| **f** | 这代表着,“传递文件的名称。”如果你不使用它,jar 假定它的输入将来自标准输入,或者,如果它正在创建一个文件,它的输出将转到标准输出。 | |
| 199 | +| **m** | 代表第一个参数是用户创建的清单文件的名称。 | |
| 200 | +| **v** | 生成详细的输出用于表述 jar 所作的事情 | |
| 201 | +| **0** | 仅存储文件;不压缩文件(用于创建放在类路径中的 JAR 文件)。 | |
| 202 | +| **M** | 不要自动创建清单文件 | |
| 203 | + |
| 204 | +如果放入 JAR 文件的文件中包含子目录,则会自动添加该子目录,包括其所有子目录等。还会保留路径信息。 |
| 205 | + |
| 206 | +以下是一些调用 jar 的典型方法。以下命令创建名为 myJarFile 的 JAR 文件。 jar 包含当前目录中的所有类文件,以及自动生成的清单文件: |
| 207 | + |
| 208 | +```shell |
| 209 | +jar cf myJarFile.jar *.class |
| 210 | +``` |
| 211 | + |
| 212 | +下一个命令与前面的示例类似,但它添加了一个名为 myManifestFile.mf 的用户创建的清单文件。 : |
| 213 | + |
| 214 | +```shell |
| 215 | +jar cmf myJarFile.jar myManifestFile.mf *.class |
| 216 | +``` |
| 217 | + |
| 218 | +这个命令输出了 myJarFile.jar 中的文件目录: |
| 219 | + |
| 220 | +```shell |
| 221 | +jar tf myJarFile.jar |
| 222 | +``` |
| 223 | + |
| 224 | +如下添加了一个“verbose”的标志,用于生成更多关于 myJarFile.jar 中文件的详细信息: |
| 225 | + |
| 226 | +```shell |
| 227 | +jar tvf myJarFile.jar |
| 228 | +``` |
| 229 | + |
| 230 | +假设 audio,classes 和 image 都是子目录,它将所有子目录组合到文件 myApp.jar 中。还包括“verbose”标志,以便在 jar 程序工作时提供额外的反馈: |
| 231 | + |
| 232 | +```shell |
| 233 | +jar cvf myApp.jar audio classes image |
| 234 | +``` |
| 235 | + |
| 236 | +如果你在创建 JAR 文件时使用了 0(零) 选项,该文件将会被替换在你的类路径(CLASSPATH)中: |
| 237 | + |
| 238 | +```shell |
| 239 | +CLASSPATH="lib1.jar;lib2.jar;" |
| 240 | +``` |
| 241 | + |
| 242 | +然后 Java 可以搜索到 lib1.jar 和 lib2.jar 的类文件。 |
| 243 | + |
| 244 | +jar 工具不像 Zip 实用程序那样通用。例如,你无法将文件添加或更新到现有 JAR 文件;只能从头开始创建 JAR 文件。 |
| 245 | + |
| 246 | +此外,你无法将文件移动到 JAR 文件中,在移动文件时将其删除。 |
| 247 | + |
| 248 | +但是,在一个平台上创建的 JAR 文件可以通过任何其他平台上的 jar 工具透明地读取(这个问题有时会困扰 Zip 实用程序)。 |
17 | 249 |
|
18 | 250 | <!-- 分页 -->
|
19 | 251 |
|
|
0 commit comments