友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
Java编程思想第4版[中文版](PDF格式)-第75部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
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 类 对应的 Java 1。1 类
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!