wc.exe
是一个常见的工具,它能统计文本文件的字符数、单词数和行数。这个项目要求写一个命令行程序,模仿已有wc.exe
的功能,并加以扩充,给出某程序设计语言源文件的字符数、单词数和行数。
实现一个统计程序,它能正确统计程序文件中的字符数、单词数、行数,以及还具备其他扩展功能,并能够快速地处理多个文件。程序处理用户需求的模式为:wc.exe [parameter] [file_name]
基本功能
wc.exe -c file.c //返回文件 file.c 的字符数
wc.exe -w file.c //返回文件 file.c 的词的数目
wc.exe -l file.c //返回文件 file.c 的行数
-s 递归处理目录下符合条件的文件。
-a 返回更复杂的数据(代码行 / 空行 / 注释行)。
?
空行:本行全部是空格或格式控制字符,如果包括代码,则只有不超过一个可显示的字符,例如“{”。
代码行:本行包括多于一个字符的代码。
注释行:本行不是代码行,并且本行包括注释。一个有趣的例子是有些程序员会在单字符后面加注释:
} //注释
在这种情况下,这一行属于注释行。
特殊情况,如 return; } //注释
既算是代码行也算是注释行。
[file_name]: 文件或目录名,可以处理一般通配符。
高级功能
-x 参数。这个参数单独使用。如果命令行有这个参数,则程序会显示图形界面,用户可以通过界面选取单个文件,程序就会显示文件的字符数、行数等全部统计信息。
算上扩展功能和高级功能,需要实现的功能如下
统计字符数:每次读取文本的一行,统计这一行的字符数,然后累加
统计单词数:将文中的中文以及其他符号用" "
取代,然后用空格分割取代后的字符串,然后分割后得到的数组转换成集合,使用集合的remove
的功能去除集合中的" "
,去除后集合的长度就是词的数目。其中使用的guava的Multiset
。
统计行数:一次读取一行,累加行数
递归处理:判断路径是不是目录,不是则报错,是的话把目录下的文件添加到集合里,目录下的子目录再递归处理,最后返回文件路径的集合。
返回复杂的数据:按所要求的进行判断累加。
通配符匹配:只能匹配*
和?
,如果只有单独一个?
则会报错,匹配后把符合条件的文件名添加到集合中,通配符中含有其他的符号或者没有这两个字符,最后返回空集合。
拿到题目后查找了I/O流的操作方法,以及正则表达式的书写,还有字符数的定义。
整个项目有三个类,一个存放主函数的类McMain
,一个存放功能函数的类McUtils
,一个是图形界面的类McView
。
主函数的逻辑图如下,在接收命令行传入的字符串之后,-x
参数具有最高优先级,-s
具有第二优先级,优先判断是否含有这两个命令。
这里主要展示用于统计的功能函数的代码
返回文件字符数
public static int characterCounting(BufferedReader br) throws IOException {
int countChar = 0;
//用于接收每一行的内容
String line = null;
while ((line = br.readLine()) != null) {
countChar += line.length();
}
return countChar;
}
返回文件单词数
public static int wordCounting(BufferedReader br) throws IOException {
//用于接收每一行的内容
String line = null;
StringBuffer stringBuffer=new StringBuffer();
while ((line = br.readLine()) != null) {
stringBuffer.append(line);
}
line = stringBuffer.toString();
line = line.replaceAll("[^a-zA-Z\\s+]", " ");
String[] strings=line.split("[\\s+,\\.\n]");
Multiset<String> col= HashMultiset.create();
for(String string:strings) {
col.add(string);
}
col.elementSet().remove("");
return col.size();
}
返回文件行数
public static int lineCounting(BufferedReader br) throws IOException {
int countLine = 0;
//用于接收每一行的内容
String line = null;
while ((line = br.readLine()) != null) {
countLine++;
}
return countLine;
}
返回代码行 / 空行 / 注释行的数目
public static int[] complexDataCounting(BufferedReader br) throws IOException {
//用于存储代码行、空行、注释行的数目
int[] data = {0, 0, 0};
//用于接收每一行的内容
String line = null;
//空白行正则表达式
String regexContainNull = "\\s*";
Pattern english = Pattern.compile("[a-zA-z]");
//用于标明下一行是不是注释行
boolean comment = false;
while ((line = br.readLine()) != null) {
line = line.trim();
if (line.matches(regexContainNull) || line.equals("{") || line.equals("}")) {
data[1]++;
} else if (line.startsWith("/*") && !line.endsWith("*/")) {
data[2]++;
comment = true;
} else if (line.startsWith("/*") && line.endsWith("*/")) {
data[2]++;
} else if (true == comment) {
data[2]++;
if (line.endsWith("*/"))
comment = false;
} else if (line.startsWith("//") || line.contains("//") ) {
data[2]++;
if(english.matcher(line).find() && line.contains("}")&& line.contains("//"))
data[0]++;
} else {
data[0]++;
}
}
return data; //0号元素为代码行,1号元素为空行,2号元素为注释行
}
递归处理目录
/**
* 遍历文件夹下的所有目录,包括子目录
* @param path
* @param list
*/
public static void recursive(String path, List<String> list) {
File file = new File(path);
//如果文件存在
if (file.exists()) {
File[] files = file.listFiles();
if (files != null) {
for (File file2 : files) {
if(file2.isDirectory()){
recursive(file2.getAbsolutePath(),list);
}else if (file2.isFile()){
list.add(file2.getAbsolutePath());
}
}
}
}
}
?
/**
* 遍历当前目录,不包括子目录
* @param path
* @param list
*/
public static void recursiveNotContainSub(String path, List<String> list){
File file = new File(path);
//如果文件存在
if (file.exists()) {
File[] files = file.listFiles();
if (files != null) {
for (File file2 : files) {
if (file2.isFile()){
list.add(file2.getAbsolutePath());
}
}
}
}
}
通配符匹配,匹配符合条件的文件
/**
* 通配符匹配
* list为待处理的文件路径集合,pattern为通配符
* @param list,pattern
* @return 匹配后的结果
*/
public static List<String> wildcardMatching(List<String> list ,String pattern) {
List<String> result = new ArrayList<String>();
if (list.isEmpty()){
return null;
}else{
if(pattern!=null){
String[] character = pattern.split("\\.");
//如果pattern只有单独一个*,不用匹配
if(pattern.length()==1 && pattern.equals("*")){
return list;
}
//遍历文件名字匹配通配符
for (String string :list){
//获取文件名
String[] fileNames = string.split("\\\\");
//如果文件名没有后缀,跳出继续遍历下一个
if(!fileNames[fileNames.length-1].contains("."))
break;
String fileName = fileNames[fileNames.length-1];
//文件名再次分割
String[] names = fileName.split("\\.");
//开始匹配通配符
//文件名开头含有*
if (character[0].startsWith("*")){
//如果文件名前缀只有一个*
if(character[0].length() == 1){
if(names[1].equals(character[1]))
result.add(string);
}else{
String[] name1 = character[0].split("\\*");
if(names[0].endsWith(name1[1]) && names[1].equals(character[1])){
result.add(string);
}
}
}
//文件名结尾含有*
else if(character[0].endsWith("*")){
String[] name1 = character[0].split("\\*");
if(names[0].startsWith(name1[0]) && names[1].equals(character[1])){
result.add(string);
}
}
//文件后缀名含有*的
else if(character[1].equals("*")){
if (character[0].equals(names[0]))
result.add(string);
if(character[0].contains("?") &&