⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 chapter10.htm

📁 Thinking In Java的中文版。Java学习的经典教程。
💻 HTM
📖 第 1 页 / 共 5 页
字号:
<br>
10.8.2 用Zip进行多文件保存<br>
提供了Zip支持的Java 1.1库显得更加全面。利用它可以方便地保存多个文件。甚至有一个独立的类来简化对Zip文件的读操作。这个库采采用的是标准Zip格式,所以能与当前因特网上使用的大量压缩、解压工具很好地协作。下面这个例子采取了与前例相同的形式,但能根据我们需要控制任意数量的命令行参数。除此之外,它展示了如何用Checksum类来计算和校验文件的“校验和”(Checksum)。可选用两种类型的Checksum:Adler32(速度要快一些)和CRC32(慢一些,但更准确)。<br>
<br>
484-486页程序<br>
<br>
对于要加入压缩档的每一个文件,都必须调用putNextEntry(),并将其传递给一个ZipEntry对象。ZipEntry对象包含了一个功能全面的接口,利用它可以获取和设置Zip文件内那个特定的Entry(入口)上能够接受的所有数据:名字、压缩后和压缩前的长度、日期、CRC校验和、额外字段的数据、注释、压缩方法以及它是否一个目录入口等等。然而,虽然Zip格式提供了设置密码的方法,但Java的Zip库没有提供这方面的支持。而且尽管CheckedInputStream和CheckedOutputStream同时提供了对Adler32和CRC32校验和的支持,但是ZipEntry只支持CRC的接口。这虽然属于基层Zip格式的限制,但却限制了我们使用速度更快的Adler32。<br>
为解压文件,ZipInputStream提供了一个getNextEntry()方法,能在有的前提下返回下一个ZipEntry。作为一个更简洁的方法,可以用ZipFile对象读取文件。该对象有一个entries()方法,可以为ZipEntry返回一个Enumeration(枚举)。<br>
为读取校验和,必须多少拥有对关联的Checksum对象的访问权限。在这里保留了指向CheckedOutputStream和CheckedInputStream对象的一个句柄。但是,也可以只占有指向Checksum对象的一个句柄。<br>
Zip流中一个令人困惑的方法是setComment()。正如前面展示的那样,我们可在写一个文件时设置注释内容,但却没有办法取出ZipInputStream内的注释。看起来,似乎只能通过ZipEntry逐个入口地提供对注释的完全支持。<br>
当然,使用GZIP或Zip库时并不仅仅限于文件——可以压缩任何东西,包括要通过网络连接发送的数据。<br>
<br>
10.8.3 Java归档(jar)实用程序<br>
Zip格式亦在Java 1.1的JAR(Java ARchive)文件格式中得到了采用。这种文件格式的作用是将一系列文件合并到单个压缩文件里,就象Zip那样。然而,同Java中其他任何东西一样,JAR文件是跨平台的,所以不必关心涉及具体平台的问题。除了可以包括声音和图像文件以外,也可以在其中包括类文件。<br>
涉及因特网应用时,JAR文件显得特别有用。在JAR文件之前,Web浏览器必须重复多次请求Web服务器,以便下载完构成一个“程序片”(Applet)的所有文件。除此以外,每个文件都是未经压缩的。但在将所有这些文件合并到一个JAR文件里以后,只需向远程服务器发出一次请求即可。同时,由于采用了压缩技术,所以可在更短的时间里获得全部数据。另外,JAR文件里的每个入口(条目)都可以加上数字化签名(详情参考Java用户文档)。<br>
一个JAR文件由一系列采用Zip压缩格式的文件构成,同时还有一张“详情单”,对所有这些文件进行了描述(可创建自己的详情单文件;否则,jar程序会为我们代劳)。在联机用户文档中,可以找到与JAR详情单更多的资料(详情单的英语是“Manifest”)。<br>
jar实用程序已与Sun的JDK配套提供,可以按我们的选择自动压缩文件。请在命令行调用它:<br>
<br>
jar [选项] 说明 [详情单] 输入文件<br>
<br>
其中,“选项”用一系列字母表示(不必输入连字号或其他任何指示符)。如下所示:<br>
<br>
487页表<br>
<br>
c 创建新的或空的压缩档<br>
t 列出目录表<br>
x 解压所有文件<br>
x file 解压指定文件<br>
f 指出“我准备向你提供文件名”。若省略此参数,jar会假定它的输入来自标准输入;或者在它创建文件时,输出会进入标准输出内<br>
m 指出第一个参数将是用户自建的详情表文件的名字<br>
v 产生详细输出,对jar做的工作进行巨细无遗的描述<br>
O 只保存文件;不压缩文件(用于创建一个JAR文件,以便我们将其置入自己的类路径中)<br>
M 不自动生成详情表文件<br>
<br>
在准备进入JAR文件的文件中,若包括了一个子目录,那个子目录会自动添加,其中包括它自己的所有子目录,以此类推。路径信息也会得到保留。<br>
下面是调用jar的一些典型方法:<br>
<br>
jar cf myJarFile.jar *.class<br>
用于创建一个名为myJarFile.jar的JAR文件,其中包含了当前目录中的所有类文件,同时还有自动产生的详情表文件。<br>
<br>
jar cmf myJarFile.jar myManifestFile.mf *.class<br>
与前例类似,但添加了一个名为myManifestFile.mf的用户自建详情表文件。<br>
<br>
jar tf myJarFile.jar<br>
生成myJarFile.jar内所有文件的一个目录表。<br>
<br>
jar tvf myJarFile.jar<br>
添加“verbose”(详尽)标志,提供与myJarFile.jar中的文件有关的、更详细的资料。<br>
<br>
jar cvf myApp.jar audio classes image<br>
假定audio,classes和image是子目录,这样便将所有子目录合并到文件myApp.jar中。其中也包括了“verbose”标志,可在jar程序工作时反馈更详尽的信息。<br>
<br>
如果用O选项创建了一个JAR文件,那个文件就可置入自己的类路径(CLASSPATH)中:<br>
CLASSPATH=&quot;lib1.jar;lib2.jar;&quot;<br>
Java能在lib1.jar和lib2.jar中搜索目标类文件。<br>
<br>
jar工具的功能没有zip工具那么丰富。例如,不能够添加或更新一个现成JAR文件中的文件,只能从头开始新建一个JAR文件。此外,不能将文件移入一个JAR文件,并在移动后将它们删除。然而,在一种平台上创建的JAR文件可在其他任何平台上由jar工具毫无阻碍地读出(这个问题有时会困扰zip工具)。<br>
正如大家在第13章会看到的那样,我们也用JAR为Java Beans打包。<br>
<br>
10.9 对象序列化<br>
Java 1.1增添了一种有趣的特性,名为“对象序列化”(Object 
Serialization)。它面向那些实现了Serializable接口的对象,可将它们转换成一系列字节,并可在以后完全恢复回原来的样子。这一过程亦可通过网络进行。这意味着序列化机制能自动补偿操作系统间的差异。换句话说,可以先在Windows机器上创建一个对象,对其序列化,然后通过网络发给一台Unix机器,然后在那里准确无误地重新“装配”。不必关心数据在不同机器上如何表示,也不必关心字节的顺序或者其他任何细节。<br>
就其本身来说,对象的序列化是非常有趣的,因为利用它可以实现“有限持久化”。请记住“持久化”意味着对象的“生存时间”并不取决于程序是否正在执行——它存在或“生存”于程序的每一次调用之间。通过序列化一个对象,将其写入磁盘,以后在程序重新调用时重新恢复那个对象,就能圆满实现一种“持久”效果。之所以称其为“有限”,是因为不能用某种“persistent”(持久)关键字简单地地定义一个对象,并让系统自动照看其他所有细节问题(尽管将来可能成为现实)。相反,必须在自己的程序中明确地序列化和组装对象。<br>
语言里增加了对象序列化的概念后,可提供对两种主要特性的支持。Java 
1.1的“远程方法调用”(RMI)使本来存在于其他机器的对象可以表现出好象就在本地机器上的行为。将消息发给远程对象时,需要通过对象序列化来传输参数和返回值。RMI将在第15章作具体讨论。<br>
对象的序列化也是Java Beans必需的,后者由Java 1.1引入。使用一个Bean时,它的状态信息通常在设计期间配置好。程序启动以后,这种状态信息必须保存下来,以便程序启动以后恢复;具体工作由对象序列化完成。<br>
对象的序列化处理非常简单,只需对象实现了Serializable接口即可(该接口仅是一个标记,没有方法)。在Java 
1.1中,许多标准库类都发生了改变,以便能够序列化——其中包括用于基本数据类型的全部封装器、所有集合类以及其他许多东西。甚至Class对象也可以序列化(第11章讲述了具体实现过程)。<br>
为序列化一个对象,首先要创建某些OutputStream对象,然后将其封装到ObjectOutputStream对象内。此时,只需调用writeObject()即可完成对象的序列化,并将其发送给OutputStream。相反的过程是将一个InputStream封装到ObjectInputStream内,然后调用readObject()。和往常一样,我们最后获得的是指向一个上溯造型Object的句柄,所以必须下溯造型,以便能够直接设置。<br>
对象序列化特别“聪明”的一个地方是它不仅保存了对象的“全景图”,而且能追踪对象内包含的所有句柄并保存那些对象;接着又能对每个对象内包含的句柄进行追踪;以此类推。我们有时将这种情况称为“对象网”,单个对象可与之建立连接。而且它还包含了对象的句柄数组以及成员对象。若必须自行操纵一套对象序列化机制,那么在代码里追踪所有这些链接时可能会显得非常麻烦。在另一方面,由于Java对象的序列化似乎找不出什么缺点,所以请尽量不要自己动手,让它用优化的算法自动维护整个对象网。下面这个例子对序列化机制进行了测试。它建立了许多链接对象的一个“Worm”(蠕虫),每个对象都与Worm中的下一段链接,同时又与属于不同类(Data)的对象句柄数组链接:<br>
<br>
490-492页程序<br>
<br>
更有趣的是,Worm内的Data对象数组是用随机数字初始化的(这样便不用怀疑编译器保留了某种原始信息)。每个Worm段都用一个Char标记。这个Char是在重复生成链接的Worm列表时自动产生的。创建一个Worm时,需告诉构建器希望它有多长。为产生下一个句柄(next),它总是用减去1的长度来调用Worm构建器。最后一个next句柄则保持为null(空),表示已抵达Worm的尾部。<br>
上面的所有操作都是为了加深事情的复杂程度,加大对象序列化的难度。然而,真正的序列化过程却是非常简单的。一旦从另外某个流里创建了ObjectOutputStream,writeObject()就会序列化对象。注意也可以为一个String调用writeObject()。亦可使用与DataOutputStream相同的方法写入所有基本数据类型(它们有相同的接口)。<br>
有两个单独的try块看起来是类似的。第一个读写的是文件,而另一个读写的是一个ByteArray(字节数组)。可利用对任何DataInputStream或者DataOutputStream的序列化来读写特定的对象;正如在关于连网的那一章会讲到的那样,这些对象甚至包括网络。一次循环后的输出结果如下:<br>
<br>
492-493页程序<br>
<br>
可以看出,装配回原状的对象确实包含了原来那个对象里包含的所有链接。<br>
注意在对一个Serializable(可序列化)对象进行重新装配的过程中,不会调用任何构建器(甚至默认构建器)。整个对象都是通过从InputStream中取得数据恢复的。<br>
作为Java 1.1特性的一种,我们注意到对象的序列化并不属于新的Reader和Writer层次结构的一部分,而是沿用老式的InputStream和OutputStream结构。所以在一些特殊的场合下,不得不混合使用两种类型的层次结构。<br>
<br>
10.9.1 寻找类<br>
读者或许会奇怪为什么需要一个对象从它的序列化状态中恢复。举个例子来说,假定我们序列化一个对象,并通过网络将其作为文件传送给另一台机器。此时,位于另一台机器的程序可以只用文件目录来重新构造这个对象吗?<br>
回答这个问题的最好方法就是做一个实验。下面这个文件位于本章的子目录下:<br>
<br>
493页中程序<br>
<br>
用于创建和序列化一个Alien对象的文件位于相同的目录下:<br>
<br>
493-494页程序<br>
<br>
该程序并不是捕获和控制违例,而是将违例简单、直接地传递到main()外部,这样便能在命令行报告它们。<br>
程序编译并运行后,将结果产生的file.x复制到名为xfiles的子目录,代码如下:<br>
<br>
494页中程序<br>
<br>
该程序能打开文件,并成功读取mystery对象中的内容。然而,一旦尝试查找与对象有关的任何资料——这要求Alien的Class对象——Java虚拟机(JVM)便找不到Alien.class(除非它正好在类路径内,而本例

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -