Java 初学者——简单的词义分析(匹配关键字、变量类型、变量名、方法名以及操作运算符)
- 1 实验目的
- 2 实验要求
- 3 问题分析
- 4 准备知识
- 4.1 常用类介绍
- 4.2 正则表达式及匹配的参考代码
- 5 代码展示
- 6 结果反馈
- 7 问题与反思
1 实验目的
熟悉并掌握 String 类、StringBuffer 类和 StringTokenizer 类的使用,并能对字符串变量进行操作。
2 实验要求
要求对一个 Java 程序进行简单的词法分析,找出其中用到的关键字、变量类型、变量名、方法名以及操作运算符,并分别打印出来。
3 问题分析
思路:利用文件输入流读入一行中的语句,定义相应词汇的正则表达式字符串,进行字符匹配,找到所要求的元素并打印出来。
先分析一下各种词汇的特点:
关键字:
关键字 | 关键字一般与其他字符串以空格间隔,除了if、else等 |
访问修饰符 | public, private, protected |
类、接口、抽象类 | class, abstract, interface, implements, extends, new, super, this |
修饰词 | static, final, native, strictfp, instanceof |
循环 | if, else, while, switch, case, default, do, break, continue, return |
包 | import, package |
数据类型 | byte, short, int, long, float, double, char, boolean, void, enum |
线程 | synchronized, volatile |
异常 | throw, throws, catch, try, finally |
断言 | assert |
瞬时的 | transient |
保留字 | goto, const |
注:有时候会出现:if(i < 10)、while(i < 10)、else:、switch(char):、default:、do{、break;、continue;这种关键字与与其他字符连结的情况,需要格外注意。
操作运算符:
操作运算符 | 不包括分隔符[]().,; |
算术运算符 | +, -, *, /, %, ++, – |
关系运算符 | ==, !=, >, <, >=, <= |
位运算符 | &, |, ^, ~, <<, >>, >>> |
逻辑运算符 | &&, ||, ! |
赋值运算符 | =, +=, -=, *=, /=, (%)=, <<=, >>=, &=, ^=, |
条件运算符(三元运算符) | (关系表达式)? 表达式1: 表达式2 |
instanceof 运算符 | ( Object reference variable ) instanceof (class/interface type) |
变量类型:变量类型一定与其他字符用空格分割开了,所以最好从一行中匹配。一般会在这样的字符串中出现: public int balance; 或 int balance = 0;
变量名:后面带 = 或;如 int balance = 0; 或 int balance;
方法名:后面带()或(参数)如 getName();
4 准备知识
4.1 常用类介绍
StringBuffer 类:又称为可变字符序列,它是一个类似于 String 的字符串缓冲区,通过某种方法调用可以改变该序列的长度和内容。我们可以使用 StringBuffer 的 append 方法将制定字符串追加到字符串序列。
StringBufffer sb = new StringBuffer();
sb.append("Hello");
FileReader 类:从InputStreamReader 类继承而来,该类按字符读取流中数据。从文件名创建一个 FileReader 并读取数据示例代码:
char [] charBuffer = new char[30];
FileReader fr = new FileReader("hello.txt");
while(fr.read(charBuffer) != -1){
System.out.println(charBuffer);
}
StringTokenizer 类:StringTokenizer 类属于 java.util 包,用于分隔字符串。
StringTokenizer 构造方法:
StringTokenizer(String str,String delim); //构造一个用来解析 str 的 StringTokenizer 对象,并提供指定的分隔符(可以提供多个分隔符)。
StringTokenizer 常用方法:
int countTokens(); //返回 nextToken 方法被调用的次数。
boolean hasMoreTokens(); //返回是否还有分隔符。
4.2 正则表达式及匹配的参考代码
正则表达式语法:
常见的要匹配的字符 | 对应的元字符 | 在正则表达式中的写法 |
a,b,c字符中的任意一个字符 | [abc] | [abc] |
除 a,b,c 以外的其他字符 | [^abc] | [^abc] |
任意一个字符 | . | . |
数字字符 | \d 或 [0-9] | [0-9] 或 \\d |
非数字字符 | \D | [^0-9] 或 \\D |
小写字母字符 | [a-z] | [a-z] |
大写字母字符 | [A-Z] | [A-Z] |
字母字符 | [a-zA-Z] | [a-zA-Z] |
可用于标识符的字符 | \w | \\w |
不可用于标识符的字符 | \W | \\W |
空格类字符(’\t’,’\n’,’\x0B’,’\f’,’\r’) | \s | \\s |
非空格类字符 | \S | \\S |
字符X出现0次或1次 | ? | X? |
字符X出现0次或多次 | * | X* |
字符X出现1次或多次 | + | X+ |
字符X恰好出现 n 次 | {n} | X{n} |
字符X至少出现 n 次 | {n,} | X{n,} |
字符X出现 n 次到 m 次 | {n,m} | X{n,m} |
“.”、"*"、"{"、"("这类作为元字符的字符 | [.]、[*]、[{]、[(] | [.]、[*]、[{]、[(] |
贪婪匹配(尽可能多的匹配字符) | .* | 例如:str = “goodgoodsgoodgoose”; 用"[g].*[g]“去匹配得到的是"goodgoodsgoodg” |
懒汉匹配(尽可能少的匹配到你想要的字符那里) | .*? | 例如:str = “goodgoodsgoodgoose”; 用"[g].*?[g]“去匹配得到的是"goodg” |
匹配参考代码:
public static void main(String[] args) {
import java.util.regex.Matcher;
import java.util.regex.Pattern;
String str = "int balance = 0;";
//balance = 0;对应的是 多个标识符 + 0个或多个非标识符 + 等号 + 多个任意字符 + 分号
String regex = "([\\w]+)\\W*[=].*?[;]";//这是对应的正则表达式,用()标出你想要的部分
Pattern p = Pattern.compile(regex); //进行编译
Matcher m = p.matcher(str); //进行匹配
if (m.find()) { //如果找到了相匹配的字符串
System.out.println(m.group(1));
//group(1)表示()内的内容;group(0)表示 regex 对应的全部内容
}
}
5 代码展示
package sampleString;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CharToString {
public static void main(String[] args) throws IOException{
//读取文本
FileReader fr = new FileReader("bank.txt"); //按字符 读取 流中数据
BufferedReader burd = new BufferedReader(fr);
String line = burd.readLine(); //读行
Vector<String> vecLine = new Vector<>();//创建存放一整行字符串的对象数组
Vector<String> vecStr = new Vector<>();//创建存放每一小节字符串的对象数组
//将文本内容分别按行和按块存储在 vecLine 和 vecStr 中
StringBuffer sb = new StringBuffer(); //新建可变字符序列
while(line != null) {
sb.append(line + " "); //修改字符串序列
vecLine.add(line); //向vecLine中添加一行字符串
line = burd.readLine(); //读取下一行
}
//利用 StringTokenizer 类来分块,并将字符串存入 vecStr 中
StringTokenizer st = new StringTokenizer(sb.toString()," \n,.");
//用空格符、换行符和.来分块
while(st.hasMoreTokens()) { //检查是否还有分隔符
String s = st.nextToken().trim().toString();
if (!vecStr.contains(s)) {
vecStr.add(s); //nextToken() 返回从当前位置到下一个分隔符的字符串
}
}
//创建存放关键字、操作运算符、变量类型、变量名、方法名的对象数组
Vector<String> vecKey = new Vector<>();
Vector<String> vecOperator = new Vector<>();
Vector<String> vecType = new Vector<>();
Vector<String> vecVar = new Vector<>();
Vector<String> vecMethod = new Vector<>();
//关键字、操作运算符、变量类型、变量名、方法名的正则表达式
//关键字
String[] regexKey = new String[] {"(public)", "(private)", "(protected)",
"(class)", "(abstract)", "(interface)", "(implements)", "(extends)",
"(new)", "(super)", "(this)",
"(static)", "(final)", "(native)", "(strictfp)", "(instanceof)",
"(if)","(else)","(while)","(switch)","(case)","(default)","(do)",
"(break)","(continue)", "(return)",
"(import)", "(package)",
"(byte)", "(short)", "(int)", "(long)", "(float)", "(double)", "(char)",
"(boolean)", "(void)", "(enum)", "(null)", "(true)", "(false)",
"(synchronized)", "(volatile)",
"(throw)", "(throws)", "(catch)", "(try)", "(finally)",
"(assert)",
"(transient)",
"(goto)", "(const)"};
//操作运算符
String[] regexOperator = new String[] {"([-+*/%><&^~!=|])","([-+*/%><&^!=|][=])",
"([+][+])","([-][-])","([<][<])","([>][>])","([&][&])","([|][|])",
"([<][<][=])","([>][>][=])","([>][>][>])",
"(.*?.*:.*)",".*?(instanceof).*?"};
//变量类型:一般会这样出现: public int balance; 或 int balance = 0; 或 int balance;
String[] regexType = new String[] {"([\\w]+) [\\w]+\\W*[=].+",
"public ([\\w]+) [\\w]+",
"private ([\\w]+) [\\w]+",
"protected ([\\w]+) [\\w]+"};
//变量名:后面带 = 或;如 int balance; 或 int balance = 0; 或int balance=0;
String[] regexVar = new String[] {"([\\w]+)[;]","([\\w]+)\\W*[=].*?[;]"};
//方法名:后面带()或(参数)
String regexMethod = "(.*)[(].*";
//变量类型 一定与其他字符用空格分割开了,所以最好从一行中匹配。
//变量名需要依靠其他字符来辨识,所以最好从一行字符串中去匹配。
for (int i = 0; i<vecLine.size();i++) {
//变量类型
for (int j = 0;j < regexType.length;j++) {
Pattern pType = Pattern.compile(regexType[j]);
Matcher mType = pType.matcher(vecLine.elementAt(i));
if (mType.find()) {
if (!vecType.contains(mType.group(1))) {
vecType.add(mType.group(1));
}
}
}
//变量名
for (int j = 0;j < regexVar.length;j++) {
Pattern pVar = Pattern.compile(regexVar[j]);
Matcher mVar = pVar.matcher(vecLine.elementAt(i));
if (mVar.find()) {
if (!vecVar.contains(mVar.group(1))) {
vecVar.add(mVar.group(1));
}
}
}
}
//关键字、操作运算符、方法名这类词汇可以直接在一小块字符中查找匹配
for (int i = 0; i < vecStr.size(); i++) {
//关键字
for (int j = 0;j < regexKey.length;j++) {
Pattern pKey = Pattern.compile(regexKey[j]);
Matcher mKey = pKey.matcher(vecStr.elementAt(i));
if (mKey.find()) {
if (!vecKey.contains(mKey.group(1))) {
vecKey.add(mKey.group(1));
}
}
}
//操作运算符
for (int j = 0;j < regexOperator.length;j++) {
Pattern pOper = Pattern.compile(regexOperator[j]);
Matcher mOper = pOper.matcher(vecStr.elementAt(i));
if (mOper.find()) {
if (!vecOperator.contains(mOper.group(1))) {
vecOperator.add(mOper.group(1));
}
}
}
//方法名
Pattern pMethod = Pattern.compile(regexMethod);
Matcher mMethod = pMethod.matcher(vecStr.elementAt(i));
if (mMethod.find()) {
if (!vecMethod.contains(mMethod.group(1))) {
vecMethod.add(mMethod.group(1));
}
}
}
//输出结果
System.out.println("关键字:" + vecKey);
System.out.println("操作运算符:" + vecOperator);
System.out.println("变量类型:" + vecType);
System.out.println("变量名:" + vecVar);
System.out.println("方法名:" + vecMethod);
burd.close();
}
}
6 结果反馈
关键字:[package, public, class, int, this, void, if, else, do, double, return]
操作运算符:[=, +, +=, >, -, -=]
变量类型:[class, String, void, double]
变量名:[bank, balance, account, name, openTime, id, money]
方法名:[UserInfo, deposit, println, withdrawal, if, getBalance, getInfo]
7 问题与反思
1.匹配变量类型过程中有些字符串难以区分。
例如:package bank; 和 int balance; 结构相同,在利用"([\w]+) [\w]+\\W*[=]*.*"匹配时容易把 package 看作是变量类型。此代码中没有进行区分,默认只提取 int balance = 0; 中的类型变量
2.此代码仅能对较简单的 Java 程序进行词义分析,无法分辨出注释内容和非注释内容。