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 程序进行词义分析,无法分辨出注释内容和非注释内容。