如何使用Java的Scanner类和正则表达式来标记输入?

仅仅出于我自己的目的,我正在尝试在Java中构建一个tokenizer,我可以在其中定义一个常规语法并根据它来标记输入。 不推荐使用StringTokenizer类,我在Scanner中发现了一些暗示我想做的事情,但没有运气的函数。 有人知道这个问题的好方法吗?

名称“扫描仪”有点误导,因为这个词通常用来表示词法分析器,而这不是Scanner的用途。 它只是你在C,Perl scanf()找到的scanf()函数的替代品。 与StringTokenizer和split() ,它设计为向前扫描,直到找到给定模式的匹配,并且在途中跳过的任何内容都作为标记返回。

另一方面,词法分析器必须检查和分类每个字符,即使它只是决定它是否可以安全地忽略它们。 这意味着,在每次匹配之后,它可以应用多个模式,直到找到一个匹配从该点开始的模式。 否则,它可能会找到序列“//”,并认为它是一个注释的开头,当它真的在一个字符串文字中时,它只是没有注意到开始的引号。

当然,它实际上要复杂得多,但我只是说明为什么像StringTokenizer, split()和Scanner这样的内置工具不适合这种任务。 但是,可以将Java的正则表达式类用于有限forms的词法分析。 事实上,添加Scanner类使得它更加容易,因为添加了新的Matcher API来支持它,即region和usePattern()方法。 这是一个基于Java正则表达式类构建的基本扫描程序的示例。

 import java.util.*; import java.util.regex.*; public class RETokenizer { static List tokenize(String source, List rules) { List tokens = new ArrayList(); int pos = 0; final int end = source.length(); Matcher m = Pattern.compile("dummy").matcher(source); m.useTransparentBounds(true).useAnchoringBounds(false); while (pos < end) { m.region(pos, end); for (Rule r : rules) { if (m.usePattern(r.pattern).lookingAt()) { tokens.add(new Token(r.name, m.start(), m.end())); pos = m.end(); break; } } pos++; // bump-along, in case no rule matched } return tokens; } static class Rule { final String name; final Pattern pattern; Rule(String name, String regex) { this.name = name; pattern = Pattern.compile(regex); } } static class Token { final String name; final int startPos; final int endPos; Token(String name, int startPos, int endPos) { this.name = name; this.startPos = startPos; this.endPos = endPos; } @Override public String toString() { return String.format("Token [%2d, %2d, %s]", startPos, endPos, name); } } public static void main(String[] args) throws Exception { List rules = new ArrayList(); rules.add(new Rule("WORD", "[A-Za-z]+")); rules.add(new Rule("QUOTED", "\"[^\"]*+\"")); rules.add(new Rule("COMMENT", "//.*")); rules.add(new Rule("WHITESPACE", "\\s+")); String str = "foo //in \"comment\"\nbar \"no //comment\" end"; List result = RETokenizer.tokenize(str, rules); for (Token t : result) { System.out.println(t); } } } 

顺便说一句,这是我在lookingAt()方法中找到的唯一好处。 :d

如果我理解你的问题,那么这里有两个标记字符串的示例方法。 您甚至不需要Scanner类,只有在您想要预先生成令牌时,或者比使用数组更加柔和地迭代它们。 如果数组足够,只需使用下面给出的String.split()。

请提供更多要求以获得更精确的答案。

  import java.util.Scanner; public class Main { public static void main(String[] args) { String textToTokenize = "This is a text that will be tokenized. I will use 1-2 methods."; Scanner scanner = new Scanner(textToTokenize); scanner.useDelimiter("i."); while (scanner.hasNext()){ System.out.println(scanner.next()); } System.out.println(" **************** "); String[] sSplit = textToTokenize.split("i."); for (String token: sSplit){ System.out.println(token); } } } 

如果这是一个简单的项目(用于学习如何工作),那么请参考Balint Pato所说的内容。

如果这是针对较大的项目,请考虑使用像JFlex这样的扫描仪生成器。 有点复杂,但更快,更强大。

这里的大多数答案都已经很出色但如果我没有指出ANTLR,那将是我的疏忽 。 我围绕这个优秀的工具创建了完整的编译器。 版本3有一些惊人的function,我推荐它用于任何需要你根据明确定义的语法解析输入的项目。