用新的正则表达式库解析字符序列

然 Java 语言以前的版本支持模式匹配,但 StreamTokenizerStringTokenizer 类却很少涉及用模式能够实现的功能。Java 1.4(以及现在的 1.4.1)发行版在 java.util.regex 包中加入了对使用正则表达式的模式匹配的支持。在 Merlin 的魔力系列的这篇文章中,John Zukowski 向您展示了如何用新的正则表达式库解析字符序列,从而使搜索模式的功能更加强大。

解析模式的文本字符串

正则表达式是根据文本匹配模式的方法 ― 类似于编译器生成类文件的工作原理。编译器在源代码中查找各种模式以便将源代码表达式转换为字节码。通过识别这些源代码模式,编译器能够只将有效的源代码表示转换为已编译的类文件。

 




回页首


什么是模式?

在正则表达式的上下文中,模式是字符序列的文本表示法。例如,如果您想知道一个字符序列中是否存在 car这个词,您会使用模式 car,因为这是精确地表示该字符串的方法。对于更复杂的模式,您可以使用特殊字符作为占位符。如果您不是要搜索 car,而是想搜索以字母 c开头并以字母 r结尾的任何文本字符串,您会使用 c*r模式,其中 *代表第一个 r前的任意多个字符。 c*r模式将匹配任何以 c开头并以 r结尾的字符串,如 cougarcavalierchrysler

 




回页首


如何指定模式表达式

模式匹配的主要部分是关于要使用什么样的表达式。 Pattern 先保存要使用的表达式,然后将其传递给 Matcher 类以便在字符序列的上下文中检查其匹配情况。例如,如果您想验证一个电子邮件地址,您可能要检查用户输入是否与这样一个模式匹配 ― 它包含一个字母数字序列,后跟一个 @ 符号,@ 后又跟两组用句点隔开的字符。这可以用表达式 /p{Alnum}+@/w+/./p{Alpha}{2,3} 来表示。(是的,这过于简化了电子邮件地址的结构,可能会排除某些有效的电子邮件地址,但它作为示例已经足够了。)

在讨论模式语言的具体细节之前,我们来仔细看一下 /p{Alnum}+@/w+/./p{Alpha}{2,3}/p{Alnum} 序列表示单个字母数字字符(A 到 Z、a 到 z 或 0 到 9)。 /p{Alnum} 后面的加号(+)被称为 量词(quantifier)。它被应用在表达式的前面部分,表示 /p{Alnum} 必须出现一次或更多次。使用星号(*)表示要出现零次或一次以上(含一次)。@ 就是意味着它必须出现在至少一个字母数字字符之后,这样整个模式匹配才能成功。 /w+/p{Alnum}+ 类似,但添加了下划线(_)。某些序列有多个表达式。反斜杠(/ .)代表句点。如果前面没有反斜杠,单独一个句点代表任意字符。最后的 /p{Alpha}{2, 3} 表示两个或三个字母字符。

只要学会了规范语言,您就能掌握模式的所有秘密。我们来看一些更常用的表达式的种类:

  • 文字(Literal):表达式内任何不具有特殊意义的字符都被看作是一个文字,并与自身匹配。
  • 量词(Quantifier):某些字符或表达式,它们被用来计算一个文字或分组可以在字符序列中出现的次数,以便该序列与表达式匹配。分组是由圆括号内的一组字符指定的。
    • ? 表示出现一次或根本不出现
    • * 表示出现零次或一次以上(含一次)
    • + 表示出现一次或多次
  • 字符类(Character class):一个字符类就是方括号内的一个字符集,其中,匹配可以是括号内的任意一个字符。您可以把字符类与量词结合在一起,例如, [acegikmoqsuwy]* 将是只包含字母表中奇数字母的任意字符序列。某些字符类是预先定义的:
    • /d ― 数字(0 到 9)
    • /D -- 非数字
    • /s -- 空白字符,如制表符或换行符
    • /S -- 非空白字符
    • /w -- 单字字符(a 到 z、A 到 Z、0 到 9 以及下划线)
    • /W -- 非单字字符(其它任意字符)
  • Posix 字符类(Posix character class):某些字符类仅在用于 US-ASCII 比较时才有效。例如:
    • /p{Lower} ― 小写字符
    • /p{Upper} ― 大写字符
    • /p{ASCII} ― 所有 ASCII 字符
    • /p{Alpha} ― 字母字符(/p{Lower} 与 /p{Upper} 相结合)
    • /p{Digit} ― 从 0 到 9 的数字
    • /p{Alnum} ― 字母数字字符
  • 范围(Range):使用短线(dash)来指定包括一定范围字符的字符类。例如, [A-J] 表示从 A 到 J 的大写字母。
  • 否定(Negation):脱字符(^)否定字符类的内容。例如, [^A-J] 表示除 A 到 J 之外的任何字符。

请参阅 Pattern API 文档(可以从 参考资料找到)了解关于序列的其它详细信息。

 




回页首


如何有效地使用模式

既然您已经了解了如何指定模式,我们就来使用它们吧。您需要让 Pattern 类编译它们,如下所示。注意,反斜杠字符(/)在 String 常量中需要转义。

Pattern pattern = Pattern.compile(
  "//p{Alnum}+@//w+//.//p{Alpha}{2,3}");

 

有了一个编译好的模式后,您可以使用 Pattern 类根据模式把一个输入行分割为一系列单字,或者使用 Matcher 类执行一些更复杂的任务。下面说明了如何分割输入字符序列,其中使用的模式指定了分隔符,而不是字:

String words[] =  pattern.split(input);

 

如果您想在一个字符序列中多次匹配一个模式,上面的代码片段是一个很好的起点。但如果您想获取特定的输入,您将需要 Patternmatcher() 方法。在给定某个输入时,这个方法将返回适当的 Matcher 类。接着,您使用 Matcher 实例遍历整个结果在输入序列中查找不同的模式匹配,或者使用 Matcher 实例作为查找-替换工具(后一种方法更好):

Matcher matcher = pattern.matcher(input);

 

要根据整个序列匹配模式,请使用 matches() 。要确定是否只有序列的一部分匹配,请使用 find()

if (matcher.find()) {
    // Found some string within input sequence
    // That matched the compiled pattern
    String match = matcher.group();
    // Process matching pattern
}





回页首


完整的示例

这两个类( PatternMatcher )就是整个模式匹配库。提出正确的正则表达式,然后使用 Matcher 类的结果,这就是这个模式匹配库要做的全部工作。在针对 Java 语言的关于正则表达式的专门书籍出现之前,请找一本关于 Perl 的好书来进一步了解特定的模式。清单 1 提供了一个完整的示例,该示例将在特定文件中查找从命令行作为输入而传入的最长单词。


清单 1.“最长的单词”示例

import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.util.*;
import java.util.regex.*;
public class Longest {
  public static void main(String args[]) {
    if (args.length != 1) {
      System.err.println("Provide a filename");
      return;
    }
    try {
      // Map File from filename to byte buffer
      FileInputStream input = 
        new FileInputStream(args[0]);
      FileChannel channel = input.getChannel();
      int fileLength = (int)channel.size();
      MappedByteBuffer buffer = channel.map(
        FileChannel.MapMode.READ_ONLY, 0, fileLength); 
      // Convert to character buffer
      Charset charset = Charset.forName("ISO-8859-1");
      CharsetDecoder decoder = charset.newDecoder();
      CharBuffer charBuffer = decoder.decode(buffer);
      // Create line pattern
      Pattern linePattern = 
        Pattern.compile(".*$", Pattern.MULTILINE);
      // Create word pattern
      Pattern wordBreakPattern = 
        Pattern.compile("[//p{Punct}//s}]");
      // Match line pattern to buffer
      Matcher lineMatcher = 
        linePattern.matcher(charBuffer);
      // Holder for longest word
      String longest = "";
      // For each line
      while (lineMatcher.find()) {
        // Get line
        String line = lineMatcher.group();
        // Get array of words on line
        String words[] = wordBreakPattern.split(line);
        // Look for longest word
        for (int i=0, n=words.length; i<n; i++) {
          if (words[i].length() > longest.length()) {
            longest = words[i];
          }
        }
      }
      // Report
      System.out.println("Longest word: " + longest);
 
      // Close
      input.close();
    } catch (IOException e) {
      System.err.println("Error processing");
    }
  }
}

转载于:http://www.ibm.com/developerworks/cn/java/j-mer0827/index.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值