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

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

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



  }  

  public PrintFile(File file)  

    throws IOException {  

    this(file。getPath());  

  }  

} ///:~  

  

注意构建器不可能捕获一个由基础类构建器“掷”出的违例。  

  

9。 快速输出数据文件  

最后,利用类似的快捷方式可创建一个缓冲输出文件,用它保存数据(与由人观看的数据格式相反):  

  

//: OutFile。java  

// Shorthand class for opening an output file  

// for data storage。  

package 。bruceeckel。tools;  

import java。io。*;  

  

public class OutFile extends DataOutputStream {  

  public OutFile(String filename)  

    throws IOException {  

    super(  

      new BufferedOutputStream(  

        new FileOutputStream(filename)));  

  }  

  public OutFile(File file)  

    throws IOException {  

    this(file。getPath());  



                                                                                          299 


…………………………………………………………Page 301……………………………………………………………

  }  

} ///:~  

  

非常奇怪的是(也非常不幸),Java 库的设计者居然没想到将这些便利措施直接作为他们的一部分标准提 

供。  



10。5。4 从标准输入中读取数据  



以Unix 首先倡导的“标准输入”、“标准输出”以及“标准错误输出”概念为基础,Java 提供了相应的 

System。in,System。out 以及System。err。贯这一整本书,大家都会接触到如何用 System。out进行标准输 

出,它已预封装成一个 PrintStream 对象。System。err 同样是一个PrintStream,但System。in 是一个原始 

的InputStream,未进行任何封装处理。这意味着尽管能直接使用 System。out 和System。err,但必须事先封 

装System。in,否则不能从中读取数据。  

典型情况下,我们希望用readLine()每次读取一行输入信息,所以需要将System。in 封装到一个 

DataInputStream 中。这是Java 1。0 进行行输入时采取的“老”办法。在本章稍后,大家还会看到 Java 1。1 

的解决方案。下面是个简单的例子,作用是回应我们键入的每一行内容:  

  

//: Echo。java  

// How to read from standard input  

import java。io。*;  

  

public class Echo {  

  public static void main(String'' args) {  

    DataInputStream in =  

      new DataInputStream(  

        new BufferedInputStream(System。in));  

    String s;  

    try {  

      while((s = in。readLine())。length() != 0)  

        System。out。println(s);  

      // An empty line terminates the program  

    } catch(IOException e) {  

      e。printStackTrace();  

    }  

  }  

} ///:~  

  

之所以要使用try 块,是由于 readLine()可能“掷”出一个 IOException。注意同其他大多数流一样,也应 

对System。in 进行缓冲。  

由于在每个程序中都要将System。in 封装到一个 DataInputStream 内,所以显得有点不方便。但采用这种设 

计方案,可以获得最大的灵活性。  



10。5。5 管道数据流  



本章已简要介绍了 PipedInputStream (管道输入流)和PipedOutputStream (管道输出流)。尽管描述不十 

分详细,但并不是说它们作用不大。然而,只有在掌握了多线程处理的概念后,才可真正体会它们的价值所 

在。原因很简单,因为管道化的数据流就是用于线程之间的通信。这方面的问题将在第 14 章用一个示例说 

明。  



10。6 StreamTokenizer  



尽管StreamTokenizer 并不是从 InputStream或 OutputStream 衍生的,但它只随同 InputStream工作,所以 

十分恰当地包括在库的 IO部分中。  

StreamTokenizer 类用于将任何 InputStream分割为一系列“记号”(Token)。这些记号实际是一些断续的 



                                                                                       300 


…………………………………………………………Page 302……………………………………………………………

文本块,中间用我们选择的任何东西分隔。例如,我们的记号可以是单词,中间用空白(空格)以及标点符 

号分隔。  

下面是一个简单的程序,用于计算各个单词在文本文件中重复出现的次数:  

  

//: SortedWordCount。java  

// Counts words in a file; outputs  

// results in sorted form。  

import java。io。*;  

import java。util。*;  

import c08。*; // Contains StrSortVector  

  

class Counter {  

  private int i = 1;  

  int read() { return i; }  

  void increment() { i++; }  

}  

  

public class SortedWordCount {  

  private FileInputStream file;  

  private StreamTokenizer st;  

  private Hashtable counts = new Hashtable();  

  SortedWordCount(String filename)  

    throws FileNotFoundException {  

    try {  

      file = new FileInputStream(filename);  

      st = new StreamTokenizer(file);  

      st。ordinaryChar('。');  

      st。ordinaryChar('…');  

    } catch(FileNotFoundException e) {  

      System。out。println(  

        〃Could not open 〃 + filename);  

      throw e;  

    }  

  }  

  void cleanup() {  

    try {  

      file。close();  

    } catch(IOException e) {  

      System。out。println(  

        〃file。close() unsuccessful〃);  

    }  

  }  

  void countWords() {  

    try {  

      while(st。nextToken() !=  

        StreamTokenizer。TT_EOF) {  

        String s;  

        switch(st。ttype) {  

          case StreamTokenizer。TT_EOL:  

            s = new String(〃EOL〃);  

            break;  

          case StreamTokenizer。TT_NUMBER:  



                                                                                             301 


…………………………………………………………Page 303……………………………………………………………

            s = Double。toString(st。nval);  

            break;  

          case StreamTokenizer。TT_WORD:  

            s = st。sval; // Already a String  

            break;  

          default: // single character in ttype  

            s = String。valueOf((char)st。ttype);  

        }  

        if(counts。containsKey(s))  

          ((Counter)counts。get(s))。increment();  

        else  

          counts。put(s; new Counter());  

      }  

    } catch(IOException e) {  

      System。out。println(  

        〃st。nextToken() unsuccessful〃);  

    }  

  }  

  Enumeration values() {  

    return counts。elements();  

  }  

  Enumeration keys() { return counts。keys(); }  

  Counter getCounter(String s) {  

    return (Counter)counts。get(s);  

  }  

  Enumeration sortedKeys() {  

    Enumeration e = counts。keys();  

    StrSortVector sv = new StrSortVector();  

    while(e。hasMoreElements())  

      sv。addElement((String)e。nextElement());  

    // This call forces a sort:  

    return sv。elements();  

  }  

  public static void main(String'' args) {  

    try {  

      SortedWordCount wc =  

        new SortedWordCount(args'0');  

      wc。countWords();  

      Enumeration keys = wc。sortedKeys();  

      while(keys。hasMoreElements()) {  

        String key = (String)keys。nextElement();  

        System。out。println(key + 〃: 〃  

                 + wc。getCounter(key)。read());  

      }  

      wc。cleanup();  

    } catch(Exception e) {  

      e。printStackTrace();  

    }  

  }  

} ///:~  

  

最好将结果按排序格式输出,但由于Java 1。0 和 Java 1。1 都没有提供任何排序方法,所以必须由自己动 



                                                                                          302 


…………………………………………………………Page 304……………………………………………………………

手。这个目标可用一个 StrSortVector 方便地达成(创建于第 8 章,属于那一章创建的软件包的一部分。记 

住本书所有子目录的起始目录都必须位于类路径中,否则程序将不能正确地编译)。  

为打开文件,使用了一个FileInputStream。而且为了将文件转换成单词,从FileInputStream 中创建了一 

个StreamTokenizer。在StreamTokenizer 中,存在一个默认的分隔符列表,我们可用一系列方法加入更多 

的分隔符。在这里,我们用ordinaryChar()指出“该字符没有特别重要的意义”,所以解析器不会把它当作 

自己创建的任何单词的一部分。例如,st。ordinaryChar('。')表示小数点不会成为解析出来的单词的一部 

分。在与Java 配套提供的联机文档中,可以找到更多的相关信息。  

在 countWords()中,每次从数据流中取出一个记号,而ttype信息的作用是判断对每个记号采取什么操作— 

—因为记号可能代表一个行尾、一个数字、一个字串或者一个字符。  

找到一个记号后,会查询Hashtable counts,核实其中是否已经以“键”(Key)的形式包含了一个记号。 

若答案是肯定的,对应的Counter (计数器)对象就会增值,指出已找到该单词的另一个实例。若答案为 

否,则新建一个Counter——因为Counter 构建器会将它的值初始化为 1,正是我们计算单词数量时的要求。  

SortedWordCount 并不属于Hashtable (散列表)的一种类型,所以它不会继承。它执行的一种特定类型的操 

作,所以尽管keys()和values() 方法都必须重新揭示出来,但仍不表示应使用那个继承,因为大量 

Hashtable 方法在这里都是不适当的。除此以外,对于另一些方法来说(比如getCounter()——用于获得一 

个特定字串的计数器;又如 sortedKeys()——用于产生一个枚举),它们最终都改变了 SortedWordCount 接 

口的形式。  

在main() 内,我们用SortedWordCount 打开和计算文件中的单词数量——总共只用了两行代码。随后,我们 

为一个排好序的键(单词)列表提取出一个枚举。并用它获得每个键以及相关的 Count (计数)。注意必须 

调用cleanup(),否则文件不能正常关闭。  

采用了 StreamTokenizer 的第二个例子将在第17 章提供。  



10。6。1 StringTokenizer   



尽管并不必要 IO 库的一部分,但StringTokenizer 提供了与 StreamTokenizer 极相似的功能,所以在这里一 

并讲述。  

StringTokenizer 的作用是每次返回字串内的一个记号。这些记号是一些由制表站、空格以及新行分隔的连 

续字符。因此,字串“Where is my cat?”的记号分别是“Where”、“is”、“my”和“cat?”。与 

StreamTokenizer 类似,我们可以指示 StringTokenizer 按照我们的愿望分割输入。但对于 

StringTokenizer,却需要向构建器传递另一个参数,即我们想使用的分隔字串。通常,如果想进行更复杂的 

操作,应使用StreamTokenizer。  

可用nextToken()向StringTokenizer 对象请求字串内的下一个记号。该方法要么返回一个记号,要么返回 

一个空字串(表示没有记号剩下)。  

作为一个例子,下述程序将执行一个有限的句法分析,查询键短语序列,了解句子暗示的是快乐亦或悲伤的 

含义。  

  

//: AnalyzeSentence。java  

// Look for particular sequences  

// within sentences。  

import java。util。*;  

  

public class AnalyzeSentence {  

  public static void main(String'' args) {  

    analyze(〃I am happy about this〃);  

    analyze(〃I am not happy about this〃);  

    analyze(〃I am not! I am happy〃);  

    analyze(〃I am sad about this〃);  

    analyze(〃I am not sad about this〃);  

    analyze(〃I am not! I am sad〃);  

    analyze(〃Are you happy about this?〃);  

    analyze(〃Are you sad about this?〃);  

    analyze(〃It's you! I am happy〃);  

    analyze(〃It's you! I am sad〃);  



                                                                                303 


…………………………………………………………Page 305……………………………………………………………

  }  

  static StringTokenizer st;  

  static void analyze(String s) {  

    prt(〃nnew sentence 》》 〃 + s);  

    boolean sad = false;  

    st = new StringTokenizer(s);  

    while (st。hasMoreTokens()) {  

      String token = next();  

      // Look until you find one of the  

      // two starting tokens:  

      if(!token。equals(〃I〃) &&  

         !token。equals(〃Are〃))  

        continue; // Top of while loop  

      if(token。equals(〃I〃)) {  

        String tk2 = next();  

        if(!tk2。equals(〃am〃)) // Must be after I  

          break; // Out of while loop  

        else {  

          String tk3 = next();  

          if(tk3。equals(〃sad〃)) {  

            sad = true;  

            break; // Out of while loop  

          }  

          if (tk3。equals(〃not〃)) {  

            String tk4 = next();  

            if(tk4。equals(〃sad〃))  

              break; // Leave sad false  

            if(tk4。equals(〃happy〃)) {  

              sad = true;  

              break;  

            }  

          }  

        }  

      }  

      if(token。equals(〃Are〃)) {  

        String tk2 = next();  

        if(!tk2。equals(〃you〃))  

          break; // Must be after Are  

        String tk3 = next();  

        if(tk3。equals(〃sad〃))  

          sad = true;  

        break; // Out of while loop  

      }  

    }  

    if(sad) prt(〃Sad detected〃);  

  }  

  static String next() {  

    if(st。hasMoreTokens()) {  

      String s = st。nextToken();  

      prt(s);  

      return s;  

    }   



                                                                                        304 


…………………………………………………………Page 306……………………………………………………………

    else  

      return 〃〃;  

  }  

  static void prt(String s) {  

    System。out。println(s);  

  }  

} ///:~  

  

对于准备分析的每个字串,我们进入一个while 循环,并将记号从那个字串中取出。请注意第一个 if 语句, 

假如记号既不是“I”,也不是“Are”,就会执行continue (返回循环起点,再一次开始)。这意味着除非 

发现一个“I”或者“Are”,才会真正得到记号。大家可能想用==代替 equals()方法,但那样做会出现不正 

常的表现,因为==比较的是句柄值,而equals() 比较的是内容。  

analyze()方法剩余部分的逻辑是搜索“I am sad”(我很忧伤、“I am nothappy”(我不快乐)或者“Are  

you sad? ”(你悲伤吗?)这样的句法格式。若没有break 语句,这方面的代码甚至可能更加散乱。大家应 

注意对一个典型的解析器来说,通常都有这些记号的一个表格,并能在读取新记号的时候用一小段代码在表 

格内移动。  

无论如何,只应将 StringTokenizer 看作StreamTokenizer 一种简单而且特殊的简化形式。然而,如果有一 

个字串需要进行记号处理,而且StringTokenizer 的功能实在有限,那么应该做的全部事情就是用 

StringBufferInputStream 将其转换到一个数据流里,再用它创建一个功能更强大的StreamTokenizer。  



10。7 Java 1。1 的 IO 流  



到这个时候,大家或许会陷入一种困境之中,怀疑是否存在IO 流的另一种设计方案,并可能要求更大的代码 

量。还有人能提出一种更古怪的设计吗?事实上,Java 1。1 对 IO 流库进行了一些重大的改进。看到 Reader 

和Writer 类时,大多数人的第一个印象(就象我一样)就是它们用来替换原来的 InputStream 和 

OutputStream 类。但实情并非如此。尽管不建议使用原始数据流库的某些功能(如使用它们,会从编译器收 

到一条警告消息),但原来的数据流依然得到了保留,以便维持向后兼容,而且:  

(1) 在老式层次结构里加入了新类,所以Sun 公司明显不会放弃老式数据流。  

(2) 在许多情况下,我们需要与新结构中的类联合使用老结构中的类。为达到这个目的,需要使用一些 

 “桥”类:InputStreamReader将一个 InputStream转换成 Reader,OutputStreamWriter 将一个 

OutputStream 转换成 Writer。  

所以与原来的 IO 流库相比,经常都要对新 IO 流进行层次更多的封装。同样地,这也属于装饰器方案的一个 

缺点——需要为额外的灵活性付出代价。  

之所以在Java 1。1 里添加了Reader 和 Writer 层次,最重要的原因便是国际化的需求。老式 IO流层次结构 

只支持8 位字节流,不能很好地控制 16 位Unicode 字符。由于Unicode 主要面向的是国际化支持(Java 内 

含的 char 是 16 位的Unicode),所以添加了Reader 和 Writer 层次,以提供对所有 IO 操作中的Unicode 的 

支持。除此之外,新库也对速度进行了优化,可比旧库更快地运行。  

与本书其他地方一样,我会试着提供对类的一个概述,但假定你会利用联机文档搞定所有的细节,比如方法 

的详尽列表等。  



10。7。1 数据的发起与接收  



Java 1。0 的几乎所有 IO流类都有对应的Java 1。1 类,用于提供内建的Unicode 管理。似乎最容易的事情就 

是“全部使用新类,再也不要用旧的”,但实际情况并没有这么简单。有些时候,由于受到库设计的一些限 

制,我们不得不使用Java 1。0 的 IO流类。特别要指出的是,在旧流库的基础上新加了 java。util。zip 库, 

它们依赖旧的流组件。所以最明智的做法是“尝试性”地使用 Reader 和 Writer 类。若代码不能通过编译, 

便知道必须换回老式库。  

下面这张表格分旧库与新库分别总结了信息发起与接收之间的对应关系。  

  

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