Skip to content

Commit

Permalink
Update 10.6 StreamTokenizer.md
Browse files Browse the repository at this point in the history
  • Loading branch information
quanke committed May 7, 2016
1 parent 0b0fa22 commit 7ed1837
Showing 1 changed file with 18 additions and 2 deletions.
20 changes: 18 additions & 2 deletions 10.6 StreamTokenizer.md
@@ -1,10 +1,11 @@
# 10.6 StreamTokenizer


10.6 StreamTokenizer
尽管StreamTokenizer并不是从InputStream或OutputStream衍生的,但它只随同InputStream工作,所以十分恰当地包括在库的IO部分中。

StreamTokenizer类用于将任何InputStream分割为一系列“记号”(Token)。这些记号实际是一些断续的文本块,中间用我们选择的任何东西分隔。例如,我们的记号可以是单词,中间用空白(空格)以及标点符号分隔。
下面是一个简单的程序,用于计算各个单词在文本文件中重复出现的次数:

```
//: SortedWordCount.java
// Counts words in a file, outputs
// results in sorted form.
Expand Down Expand Up @@ -103,20 +104,32 @@ public class SortedWordCount {
}
}
} ///:~
```

最好将结果按排序格式输出,但由于Java 1.0和Java 1.1都没有提供任何排序方法,所以必须由自己动手。这个目标可用一个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.
Expand Down Expand Up @@ -193,8 +206,11 @@ public class AnalyzeSentence {
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。

0 comments on commit 7ed1837

Please sign in to comment.