https://github.com/edwardliu-aurora/wordcount
基本要求
[x] -c 统计文件字符数 (实现)
[x] -w 统计文件词数 (实现)
[x] -l 统计文件行数(实现)
扩展功能
高级功能
psp2.1 | personal software process stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
planning | 计划 | 5 | 5 |
· estimate | · 估计这个任务需要多少时间 | 600 | 730 |
development | 开发 | 480 | 610 |
· analysis | · 需求分析 (包括学习新技术) | 60 | 60 |
· design spec | · 生成设计文档 | 60 | 60 |
· design review | · 设计复审 (和同事审核设计文档) | 30 | 30 |
· coding standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 10 |
· design | · 具体设计 | 30 | 60 |
· coding | · 具体编码 | 120 | 240 |
· code review | · 代码复审 | 30 | 30 |
· test | · 测试(自我测试,修改代码,提交修改) | 120 | 120 |
reporting | 报告 | 120 | 120 |
· test report | · 测试报告 | 60 | 60 |
· size measurement | · 计算工作量 | 30 | 30 |
· postmortem & process improvement plan | · 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 605 | 735 |
定义:返回文件中除去的字符总数(中文字符分离出来计算)
思路:使用 java 按行读取文件,每个行就是一个 string 对象。
使用 string.length() 来统计该行的字符数,并且按照 character 的值范围判断是否为中文字。使用两个 int 变量来计算总的字符数以及总的中文字符数。
定义:不包含中文字符,只包含 0-9,a-z,a-z 和 _ 的连续字段称为词汇
思路:查看 java 根据以上规则,编写符合的正则表达式,使用正则表达式进行按行累加单词数。
定义:返回文件中总行数(根据换行符决定)
思路:根据 java 按行读取文件,设定计数器。
定义:该目录及子目录下的文件全部分析,附带用户需要的数据
思路:使用一个函数,将该目录下的所有符合条件的文件路径转成一个 arraylist 对象并且返回到 main 函数,由 main 函数继续处理。
代码行:除了格式控制符号(如 "{}" "()" ";" 等)之外,包含多余一个字符的代码;
思路:设置一个 set 里面存储了所有的格式控制字符,如果检测到字符不在 set 内,则判断为代码行(要注意的跟注释行冲突的情况:
1. 当该行有 // 和 /* 时,观察哪一个在前面 2. 当该行在 /* */ 注释内时,则不属于代码行; 3. 当该行是 /* 注释行第一行或者末尾一行的时候,要注意检测 /* 前 或 */ 后 的字符; 4. 当该行仅包含 // 时,检查 // 前的符号
注释行:包括注释的行号,无论本行是不是代码行;即包含 // 或在 /* */ 范围内的行;
思路:按行读取,按照 // 或者 /* */ 区分情况
空行:全是空格或者格式控制字符的行;
思路:按照 正则表达式 和 string.indexof() 函数进行匹配处理
定义:可以按照 * 代表任意 0 ~ 多个 字符以及 ? 代表 1 个任意字符进行匹配
思路:先将 ? 和 * 替换为特定的正则表达式表示,然后将 . 替换为正则表达式表示,然后进行每一个路径中的正则匹配。
/
记录总字符数和中文字符数的类
/
public class charcount {
// 全体字符数目
long allcharcount = 0;
// 中文字符数目
long chncharcount = 0;
public charcount(long allcharcount, long chncharcount) { this.allcharcount = allcharcount; this.chncharcount = chncharcount; } public long getallcharcount() { return allcharcount; } public void setallcharcount(long allcharcount) { this.allcharcount = allcharcount; } public long getchncharcount() { return chncharcount; } public void setchncharcount(long chncharcount) { this.chncharcount = chncharcount; }
}
- linecount: - blanklinecount 空白行数统计 - codelinecount 代码行数统计 - commentlinecount 注释行数统计
package bean;
// 记录详细行数的类
public class linecount {
// 空行
int blanklinecount = 0;
// 代码行
int codelinecount = 0;
// 注释行
int commentlinecount = 0;
public linecount(int blanklinecount, int codelinecount, int commentlinecount) { this.blanklinecount = blanklinecount; this.codelinecount = codelinecount; this.commentlinecount = commentlinecount; } public int getblanklinecount() { return blanklinecount; } public void setblanklinecount(int blanklinecount) { this.blanklinecount = blanklinecount; } public int getcodelinecount() { return codelinecount; } public void setcodelinecount(int codelinecount) { this.codelinecount = codelinecount; } public int getcommentlinecount() { return commentlinecount; } public void setcommentlinecount(int commentlinecount) { this.commentlinecount = commentlinecount; }
}
- basicstatistic: - public charcount getcharcount(string filepath) 返回字数统计
// 返回文件字符数的函数
public charcount getcharcount(string filepath){
// 全体字符数变量和中文字符数变量
long allcharcount = 0, chncharcount = 0;
// 新建 nio 文件路径对象
path path = paths.get(filepath);
// 为了避免文本太大,这里采用惰性的 stream
- public long getwordcount(string filepath) 返回词数统计
// 返回文件的词汇数
public long getwordcount(string filepath){
long wordcount = 0;
// 为了避免文本太大,这里采用惰性的 stream
- public long getlinecount(string filepath) 返回行数统计
// 返回文件的行数
public long getlinecount(string filepath){
long linecount = 0;
// 为了避免文本太大,这里采用惰性的 stream
- extrastatistic: - public linecount getdetaillinecount(string filepath) 获取详细的行数信息
package service;
import bean.linecount;
import java.io.bufferedreader;
import java.io.fileinputstream;
import java.io.inputstreamreader;
import java.nio.file.files;
import java.nio.file.path;
import java.nio.file.paths;
import java.util.regex.matcher;
import java.util.regex.pattern;
import java.util.stream.stream;
// 高级统计功能
public class extrastatistic {
public linecount getdetaillinecount(string filepath) { linecount linecount = new linecount(0,0,0); // 正则表达式匹配任何非空字符 pattern pattern = pattern.compile("\\s"); // 统计空行数 path path = paths.get(filepath); try(stream<string> lines = files.lines(path, utils.charsetrecognize(filepath))){ linecount.setblanklinecount( (int) lines.filter(line -> { if(line.length() == 0) return true; int i = 0; matcher matcher = pattern.matcher(line); while(matcher.find()){ i++; // 如果有超过一个非空白字符,则不为空行 if(i > 1) return false; } // 其余为空行 return true; }).count() ); } catch(exception e){ e.printstacktrace(); system.out.println("文件不存在或无法访问"); return null; } // 统计注释行和代码行 try(bufferedreader bufferedreader = new bufferedreader( new inputstreamreader( new fileinputstream(filepath), utils.charsetrecognize(filepath) ))){ int commentlinecount = 0; int codelinecount = 0; // 按行读取文件 for(string line = bufferedreader.readline(); line != null; line = bufferedreader.readline()) { // 单行注释符号位置 int onelinepos = line.indexof("//"); // 多行注释符号位置 int mullinepos = line.indexof("/*"); // 如果该行有 //,且 // 在前,则将第一次匹配到的 // 后的内容删去,注释行 +1 if(onelinepos >= 0 && (mullinepos < 0 || (mullinepos >= 0 && onelinepos < mullinepos))){ line = line.substring(0,onelinepos); commentlinecount++; // 如果有 >1 个非空字符,则同时也为代码行 matcher matcher = pattern.matcher(line); int i = 0; while(matcher.find()) i++; if(i > 1) codelinecount++; } // 如果该行只有 /* ,则检查是否同时为代码行。注释行 +1,连续读取直到遇到 */ 行 else if(mullinepos >= 0){ line = line.substring(0, mullinepos); commentlinecount++; // 如果有 >1 个非空字符,则同时也为代码行 matcher matcher = pattern.matcher(line); int i = 0; while(matcher.find()) i++; if(i > 1) codelinecount++; line = bufferedreader.readline(); while(line.indexof("*/") < 0) { commentlinecount++; line = bufferedreader.readline(); } commentlinecount++; line = line.substring(line.indexof("*/")+2); // 如果有超过一个非空字符,则也为代码行 i = 0; matcher = pattern.matcher(line); while(matcher.find()) i++; if(i > 1) codelinecount++; } // 如果没有注释,则看是否能匹配到 >1 个非空字符 else{ int i = 0; matcher matcher = pattern.matcher(line); while(matcher.find()) i++; if(i > 1) codelinecount++; } } linecount.setcodelinecount(codelinecount); linecount.setcommentlinecount(commentlinecount); } catch(exception e){ e.printstacktrace(); system.out.println("文件不存在或无法访问"); return null; } return linecount; }
}
- utils: - public static charset charsetrecognize(string filepath) 识别文本的编码类型(仅支持 gbk 和 utf-8)
// 文件编码类型简单识别
public static charset charsetrecognize(string filepath){
try{
file file = new file(filepath);
inputstream in = new java.io.fileinputstream(file);
byte[] b = new byte[3];
in.read(b);
in.close();
if (b[0] == -17 && b[1] == -69 && b[2] == -65)
return charset.forname("utf-8");
else{
try (stream
- public static arraylist<string> getfilespath(string folderpath,string filepattern) 返回某目录下的所有符合 filepattern 通配符的文件路径
// 获取一个目录下及其子目录下的所有的文件路径
public static arraylist
- main: - 主要负责输入输出以及以上函数的合理调用
package com.edwardliu_aurora;
import bean.charcount;
import bean.linecount;
import service.basicstatistic;
import service.extrastatistic;
import service.utils;
import java.io.file;
import java.util.arraylist;
public class main {
public static void main(string[] args) {
boolean charcount = false;
boolean wordcount = false;
boolean linecount = false;
boolean directory = false;
boolean detailline = false;
for(int i=0;i<args.length-1;i++){
if(args[i].equals("-c")) charcount = true;
else if(args[i].equals("-w")) wordcount = true;
else if(args[i].equals("-l")) linecount = true;
else if(args[i].equals("-s")) directory = true;
else if(args[i].equals("-a")) detailline = true;
}
basicstatistic basicstatistic = new basicstatistic();
extrastatistic extrastatistic = new extrastatistic();
if(directory) {
string filepattern = args[args.length-1];
filepattern = filepattern.
replaceall("\?","[^/\\\\:*?<>|]").
replaceall("\","[^/\\\\:*?<>|]").
replaceall("\.","\\.");
arraylist
已经在上方图片展示
我在这个项目中使用了 java 1.8 中才开始支持的 stream api。好处是可以支持函数式编程,可以很方便地用并行运算对文件进行统计工作。而同时这也带来了一个问题——用户必须使用 jre 1.8+ 的版本才能运行我的程序。
在这个项目中,我在读取文件时发现了一个文件编码的识别问题。文件编码的识别问题本身比较复杂,因为 windows 下的文本文件,有的带有 bom 头信息,而有的没有携带 bom 头信息。 对于有携带 bom 头信息的,我可以很方便地识别出该文件是否为 utf-8 编码。然而,有很多文件是没有 bom 编码的,我只能根据异常来猜测这个文件是什么编码的。所以目前我的编码识别函数只能支持 utf-8 和 gbk 两种编码,并没有支持其他编码。一旦用户的文本文件是其他编码的,我的程序会出现不可预知的错误。
我没有对用户输入的参数进行判断和校验。我只对是否存在这个文件做了简单的校验。一旦用户把命令输入错误了,我的程序将发生无法预料的错误。
使用软件工程的方法来进行项目的设计,前期也许会花费很多时间,但是事后在编写的过程中会更加清晰有条理,让整体项目设计变得更加可控。
如对本文有疑问, 点击进行留言回复!!
Springboot项目因为kackson版本问题启动报错解决方案
Java多线程下的其他组件之CyclicBarrier、Callable、Future和FutureTask详解
网友评论