1. 简介

在本篇指南中,我们将学习如何使用 Java 中的 StreamTokenizer 类,将字符流解析成一个个有意义的 token。这个类虽然古老,但在某些场景下依然实用,比如解析配置文件、简易脚本语言等。

2. StreamTokenizer 概述

StreamTokenizer 是一个逐字符读取输入流的工具类。每个字符可以具有以下一种或多种属性:

  • 空格(whitespace)
  • 字母(alphabetic)
  • 数字(numeric)
  • 字符串引号(string quote)
  • 注释字符(comment character)

默认配置说明

在默认配置下,StreamTokenizer 将以下字符视为特定类型:

类型 示例字符
单词字符(Word) a-z, A-Z
数字字符 0-9
空格字符 ASCII 0~32(如空格、换行)
注释字符 /
字符串引号 '"

⚠️ 注意:默认情况下,换行符被视为 whitespace,而不是独立的 token,并且不支持 C/C++ 风格的注释。

常用字段说明

StreamTokenizer 提供了几个常量用于判断 token 类型:

  • TT_EOF:流结束
  • TT_EOL:行结束
  • TT_NUMBER:数字 token
  • TT_WORD:单词 token

同时,它提供了几个字段用于获取当前 token 的值:

  • ttype:当前 token 的类型
  • sval:如果是字符串或单词,保存其值
  • nval:如果是数字,保存其值(double 类型)

3. 默认配置示例

我们来看一个使用默认配置的完整示例:

private static final int QUOTE_CHARACTER = '\'';
private static final int DOUBLE_QUOTE_CHARACTER = '"';

public static List<Object> streamTokenizerWithDefaultConfiguration(Reader reader) throws IOException {
    StreamTokenizer streamTokenizer = new StreamTokenizer(reader);
    List<Object> tokens = new ArrayList<>();

    int currentToken = streamTokenizer.nextToken();
    while (currentToken != StreamTokenizer.TT_EOF) {

        if (streamTokenizer.ttype == StreamTokenizer.TT_NUMBER) {
            tokens.add(streamTokenizer.nval);
        } else if (streamTokenizer.ttype == StreamTokenizer.TT_WORD
                || streamTokenizer.ttype == QUOTE_CHARACTER
                || streamTokenizer.ttype == DOUBLE_QUOTE_CHARACTER) {
            tokens.add(streamTokenizer.sval);
        } else {
            tokens.add((char) currentToken);
        }

        currentToken = streamTokenizer.nextToken();
    }

    return tokens;
}

测试内容

我们测试的输入文件内容如下:

3 quick brown foxes jump over the "lazy" dog!
#test1
//test2

输出结果

Number: 3.0
Word: quick
Word: brown
Word: foxes
Word: jump
Word: over
Word: the
Word: lazy
Word: dog
Ordinary char: !
Ordinary char: #
Word: test1

关键字段说明

  • ttype:表示当前 token 的类型
  • sval:当 token 是单词或字符串时,保存其字符串值
  • nval:当 token 是数字时,保存其 double 值

例如,字符串 "lazy"ttype'"',而 sval"lazy"

4. 自定义配置

StreamTokenizer 提供了多种方法用于修改默认行为,从而满足不同场景的需求。

示例:修改配置

public static List<Object> streamTokenizerWithCustomConfiguration(Reader reader) throws IOException {
    StreamTokenizer streamTokenizer = new StreamTokenizer(reader);
    List<Object> tokens = new ArrayList<>();

    streamTokenizer.wordChars('!', '-');
    streamTokenizer.ordinaryChar('/');
    streamTokenizer.commentChar('#');
    streamTokenizer.eolIsSignificant(true);

    // 以下代码与之前相同
    int currentToken = streamTokenizer.nextToken();
    while (currentToken != StreamTokenizer.TT_EOF) {
        // ...
        currentToken = streamTokenizer.nextToken();
    }

    return tokens;
}

新输出结果

Word: "lazy"
Word: dog!
Ordinary char: 
Ordinary char: 
Ordinary char: /
Ordinary char: /
Word: test2

配置说明

  • wordChars('!', '-'):将 !- 之间的字符设为单词字符
  • ordinaryChar('/'):将 / 设为普通字符(不再是注释)
  • commentChar('#'):将 # 设为新的注释字符
  • eolIsSignificant(true):将换行符视为独立 token

这样一来,我们可以更灵活地控制解析行为,比如将 # 后的内容跳过、将 / 作为普通字符处理等。

其他配置方法

除了上述配置外,还可以使用以下方法进行更复杂的设置:

  • quoteChar(int ch):设置新的字符串引号字符
  • whitespaceChars(int low, int hi):设置新的空格字符范围
  • parseNumbers():启用数字解析(默认已启用)
  • resetSyntax():重置语法状态

通过组合这些方法,你可以实现非常灵活的字符流解析逻辑。

5. 小结

本篇指南中,我们学习了 Java 中 StreamTokenizer 的基本使用方式,包括:

✅ 默认配置下的 token 解析
✅ 如何根据需要修改配置
✅ 常用字段 ttype, sval, nval 的作用
✅ 灵活使用各种方法实现自定义词法分析

虽然 StreamTokenizer 类较为古老,但在处理简单文本解析时仍是一个实用的工具。如果你正在编写一个小型脚本解析器、配置文件读取器等,不妨考虑使用它。

如需完整代码,可前往 GitHub 查看。


原始标题:Guide to StreamTokenizer