友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
第三电子书 返回本书目录 加入书签 我的书架 我的书签 TXT全本下载 『收藏到我的浏览器』

Java编程思想第4版[中文版](PDF格式)-第147部分

快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!


// warranty of merchantability; fitness for a  

// particular purpose or non…infringement。 The entire  

// risk as to the quality and performance of the  

// software is with you。 Bruce Eckel and the  

// publisher shall not be liable for any damages  

// suffered by you or any third party as a result of  

// using or distributing software。 In no event will  

// Bruce Eckel or the publisher be liable for any  

// lost revenue; profit; or data; or for direct;  

// indirect; special; consequential; incidental; or  

// punitive damages; however caused and regardless of  

// the theory of liability; arising out of the use of  

// or inability to use software; even if Bruce Eckel  

// and the publisher have been advised of the  

// possibility of such damages。 Should the software  

// prove defective; you assume the cost of all  

// necessary servicing; repair; or correction。 If you  

// think you've found an error; please email all  

// modified files with clearly mented changes to:  

// Bruce@EckelObjects。。 (please use the same  

// address for non…code errors found in the book)。  

//////////////////////////////////////////////////  

  

从一个打包文件中提取文件时,当初所用系统的文件分隔符也会标注出来,以便用本地系统适用的符号替换 

它。  

当前章的子目录保存在 chapter字段中,它初始化成c02 (大家可注意一下第2 章的列表正好没有包含一个 

打包语句)。只有在当前文件里发现一个 package (打包)语句时,chapter字段才会发生改变。  

  

1。 构建一个打包文件  

第一个构建器用于从本书的ASCII 文本版里提取出一个文件。发出调用的代码(在列表里较深的地方)会读 

入并检查每一行,直到找到与一个列表的开头相符的为止。在这个时候,它就会新建一个SourceCodeFile 对 

象,将第一行的内容(已经由调用代码读入了)传递给它,同时还要传递BufferedReader 对象,以便在这个 

缓冲区中提取源码列表剩余的内容。  

从这时起,大家会发现 String 方法被频繁运用。为提取出文件名,需调用substring()的过载版本,令其从 

一个起始偏移开始,一直读到字串的末尾,从而形成一个“子串”。为算出这个起始索引,先要用length() 

得出 startMarker 的总长,再用trim()删除字串头尾多余的空格。第一行在文件名后也可能有一些字符;它 

们是用 indexOf()侦测出来的。若没有发现找到我们想寻找的字符,就返回…1;若找到那些字符,就返回它 

们第一次出现的位置。注意这也是 indexOf()的一个过载版本,采用一个字串作为参数,而非一个字符。  

解析出并保存好文件名后,第一行会被置入字串contents 中(该字串用于保存源码清单的完整正文)。随 

后,将剩余的代码行读入,并合并进入contents 字串。当然事情并没有想象的那么简单,因为特定的情况需 

加以特别的控制。一种情况是错误检查:若直接遇到一个 startMarker (起始标记),表明当前操作的这个 



                                                                                          631 


…………………………………………………………Page 633……………………………………………………………

代码列表没有设置一个结束标记。这属于一个出错条件,需要退出程序。  

另一种特殊情况与package 关键字有关。尽管Java 是一种自由形式的语言,但这个程序要求 package 关键字 

必须位于行首。若发现 package 关键字,就通过检查位于开头的空格以及位于末尾的分号,从而提取出包名 

 (注意亦可一次单独的操作实现,方法是使用过载的substring(),令其同时检查起始和结束索引位置)。 

随后,将包名中的点号替换成特定的文件分隔符——当然,这里要假设文件分隔符仅有一个字符的长度。尽 

管这个假设可能对目前的所有系统都是适用的,但一旦遇到问题,一定不要忘了检查一下这里。  

默认操作是将每一行都连接到 contents 里,同时还有换行字符,直到遇到一个 endMarker (结束标记)为 

止。该标记指出构建器应当停止了。若在 endMarker 之前遇到了文件结尾,就认为存在一个错误。  

  

2。 从打包文件中提取  

第二个构建器用于将源码文件从打包文件中恢复(提取)出来。在这儿,作为调用者的方法不必担心会跳过 

一些中间文本。打包文件包含了所有源码文件,它们相互间紧密地靠在一起。需要传递给该构建器的仅仅是 

一个BufferedReader,它代表着“信息源”。构建器会从中提取出自己需要的信息。但在每个代码列表开始 

的地方还有一些配置信息,它们的身份是用packMarker (打包标记)指出的。若packMarker 不存在,意味 

着调用者试图用错误的方法来使用这个构建器。  

一旦发现packMarker,就会将其剥离出来,并提取出目录名(用一个'#'结尾)以及文件名(直到行末)。 

不管在哪种情况下,旧分隔符都会被替换成本地适用的一个分隔符,这是用String replace()方法实现的。 

老的分隔符被置于打包文件的开头,在代码列表稍靠后的一部分即可看到是如何把它提取出来的。  

构建器剩下的部分就非常简单了。它读入每一行,把它合并到 contents 里,直到遇见endMarker 为止。  

  

3。 程序列表的存取  

接下来的一系列方法是简单的访问器:directory()、filename() (注意方法可能与字段有相同的拼写和大小 

写形式)和 contents()。而hasFile()用于指出这个对象是否包含了一个文件(很快就会知道为什么需要这 

个)。  

最后三个方法致力于将这个代码列表写进一个文件——要么通过writePacked()写入一个打包文件,要么通 

过writeFile()写入一个Java 源码文件。writePacked() 需要的唯一东西就是DataOutputStream,它是在别 

的地方打开的,代表着准备写入的文件。它先把头信息置入第一行,再调用writeBytes()将 contents (内 

容)写成一种“通用”格式。  

准备写 Java 源码文件时,必须先把文件建好。这是用 IO。psOpen()实现的。我们需要向它传递一个File 对 

象,其中不仅包含了文件名,也包含了路径信息。但现在的问题是:这个路径实际存在吗?用户可能决定将 

所有源码目录都置入一个完全不同的子目录,那个目录可能是尚不存在的。所以在正式写每个文件之前,都 

要调用File。mkdirs() 方法,建好我们想向其中写入文件的目录路径。它可一次性建好整个路径。  

  

4。 整套列表的包容  

以子目录的形式组织代码列表是非常方便的,尽管这要求先在内存中建好整套列表。之所以要这样做,还有 

另一个很有说服力的原因:为了构建更“健康”的系统。也就是说,在创建代码列表的每个子目录时,都会 

加入一个额外的文件,它的名字包含了那个目录内应有的文件数目。  

DirMap 类可帮助我们实现这一效果,并有效地演示了一个“多重映射”的概述。这是通过一个散列表 

 (Hashtable)实现的,它的“键”是准备创建的子目录,而“值”是包含了那个特定目录中的 

SourceCodeFile 对象的Vector 对象。所以,我们在这儿并不是将一个“键”映射(或对应)到一个值,而 

是通过对应的Vector,将一个键“多重映射”到一系列值。尽管这听起来似乎很复杂,但具体实现时却是非 

常简单和直接的。大家可以看到,DirMap 类的大多数代码都与向文件中的写入有关,而非与“多重映射”有 

关。与它有关的代码仅极少数而已。  

可通过两种方式建立一个DirMap (目录映射或对应)关系:默认构建器假定我们希望目录从当前位置向下展 

开,而另一个构建器让我们为起始目录指定一个备用的“绝对”路径。  

add()方法是一个采取的行动比较密集的场所。首先将directory()从我们想添加的SourceCodeFile 里提取 

出来,然后检查散列表(Hashtable),看看其中是否已经包含了那个键。如果没有,就向散列表加入一个新 

的Vector,并将它同那个键关联到一起。到这时,不管采取的是什么途径,Vector 都已经就位了,可以将它 

提取出来,以便添加SourceCodeFile。由于Vector 可象这样同散列表方便地合并到一起,所以我们从两方 

面都能感觉得非常方便。  

写一个打包文件时,需打开一个准备写入的文件(当作DataOutputStream 打开,使数据具有“通用”性), 

并在第一行写入与老的分隔符有关的头信息。接着产生对 Hashtable 键的一个 Enumeration (枚举),并遍 



                                                                 632 


…………………………………………………………Page 634……………………………………………………………

历其中,选择每一个目录,并取得与那个目录有关的Vector,使那个Vector 中的每个SourceCodeFile 都能 

写入打包文件中。  

用write()将Java 源码文件写入它们对应的目录时,采用的方法几乎与 writePackedFile()完全一致,因为 

两个方法都只需简单调用SourceCodeFile 中适当的方法。但在这里,根路径会传递给 

SourceCodeFile。writeFile()。所有文件都写好后,名字中指定了已写文件数量的那个附加文件也会被写 

入。  

  

5。 主程序  

前面介绍的那些类都要在CodePackager 中用到。大家首先看到的是用法字串。一旦最终用户不正确地调用了 

程序,就会打印出介绍正确用法的这个字串。调用这个字串的是usage()方法,同时还要退出程序。main() 

唯一的任务就是判断我们希望创建一个打包文件,还是希望从一个打包文件中提取什么东西。随后,它负责 

保证使用的是正确的参数,并调用适当的方法。  

创建一个打包文件时,它默认位于当前目录,所以我们用默认构建器创建DirMap。打开文件后,其中的每一 

行都会读入,并检查是否符合特殊的条件:  

(1) 若行首是一个用于源码列表的起始标记,就新建一个SourceCodeFile 对象。构建器会读入源码列表剩下 

的所有内容。结果产生的句柄将直接加入DirMap。  

(2) 若行首是一个用于源码列表的结束标记,表明某个地方出现错误,因为结束标记应当只能由 

SourceCodeFile 构建器发现。  

  

提取/释放一个打包文件时,提取出来的内容可进入当前目录,亦可进入另一个备用目录。所以需要相应地 

创建DirMap 对象。打开文件,并将第一行读入。老的文件路径分隔符信息将从这一行中提取出来。随后根据 

输入来创建第一个 SourceCodeFile 对象,它会加入DirMap。只要包含了一个文件,新的 SourceCodeFile 对 

象就会创建并加入(创建的最后一个用光输入内容后,会简单地返回,然后hasFile()会返回一个错误)。  



17。1。2 检查大小写样式  



尽管对涉及文字处理的一些项目来说,前例显得比较方便,但下面要介绍的项目却能立即发挥作用,因为它 

执行的是一个样式检查,以确保我们的大小写形式符合“事实上”的 Java 样式标准。它会在当前目录中打开 

每个。java 文件,并提取出所有类名以及标识符。若发现有不符合Java 样式的情况,就向我们提出报告。  

为了让这个程序正确运行,首先必须构建一个类名,将它作为一个“仓库”,负责容纳标准 Java 库中的所有 

类名。为达到这个目的,需遍历用于标准 Java 库的所有源码子目录,并在每个子目录都运行 

ClassScanner。至于参数,则提供仓库文件的名字(每次都用相同的路径和名字)和命令行开关…a,指出类 

名应当添加到该仓库文件中。  

为了用程序检查自己的代码,需要运行它,并向它传递要使用的仓库文件的路径与名字。它会检查当前目录 

中的所有类和标识符,并告诉我们哪些没有遵守典型的Java 大写写规范。  

要注意这个程序并不是十全十美的。有些时候,它可能报告自己查到一个问题。但当我们仔细检查代码的时 

候,却发现没有什么需要更改的。尽管这有点儿烦人,但仍比自己动手检查代码中的所有错误强得多。  

下面列出源代码,后面有详细的解释:  

  

//: ClassScanner。java  

// Scans all files in directory for classes  

// and identifiers; to check capitalization。  

// Assumes properly piling code listings。  

// Doesn't do everything right; but is a very  

// useful aid。  

import java。io。*;  

import java。util。*;  

  

class MultiStringMap extends Hashtable {  

  public void add(String key; String value) {  

    if(!containsKey(key))  

      put(key; new Vector());  

    ((Vector)get(key))。addElement(value);  



                                                                             633 


…………………………………………………………Page 635……………………………………………………………

  }  

  public Vector getVector(String key) {  

    if(!containsKey(key)) {  

      System。err。println(  

        〃ERROR: can't find key: 〃 + key);  

      System。exit(1);  

    }  

    return (Vector)get(key);  

  }  

  public void printValues(PrintStream p) {  

    Enumeration k = keys();  

    while(k。hasMoreElements()) {  

      String oneKey = (String)k。nextElement();  

      Vector val = getVector(oneKey);  

      for(int i = 0; i 《 val。size(); i++)  

        p。println((String)val。elementAt(i));  

    }  

  }  

}  

  

public class ClassScanner {  

  private File path;  

  private String'' fileList;  

  private Properties classes = new Properties();  

  private MultiStringMap   

    classMap = new MultiStringMap();  

    identMap = new MultiStringMap();  

  private StreamTokenizer in;  

  pu
返回目录 上一页 下一页 回到顶部 1 1
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!