Java编程思想第4版[中文版](PDF格式)-第82部分
按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 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 ha