WC个人项目(JAVA实现)
一、Github地址:https://github.com/GordonKowk/WC_Item
二、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 36 |
· Estimate | · 估计这个任务需要多少时间 | 30 | 30 |
Development | 开发 | 960 | 784 |
· Analysis | · 需求分析 | 50 | 50 |
· Design Spec | · 生成设计文档 | 30 | 43 |
· Design Review | · 设计复审 | 20 | 20 |
· Coding Standard | · 代码规范 | 20 | 20 |
· Design | · 具体设计 | 80 | 60 |
· Coding | · 具体编码 | 750 | 681 |
· Code Review | · 代码复审 | 30 | 30 |
· Test | · 测试(自我测试,修改代码,提交修改) | 180 | 180 |
Reporting | 报告 | 60 | 85 |
· Test Report | · 测试报告 | 60 | 60 |
· Size Measurement | · 计算工作量 | 20 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 40 | 40 |
合计 | 2360 | 2159 |
三、遇到的问题&解题思路:
1)一开始着手这个项目的时候挺苦恼的,因为我第一次用cmd去运行一个java文件(妈呀,我才发现调控台原来是命令行),但是学习过后也是比较简单的。项目做出了多个java文件,就在命令行里编译多个,最后选出主类去运行。因为我用的是MyEclipse,写出的java自带包,必须去掉包后才能够让cmd运行时不报错。
2)不论是什么功能,先写入一个switch去判断每一个Processro,从简单的入手,基础功能-c,-w,-l,-a的功能都是我们课堂接触过的,如何把文件内容送入缓冲区也是学过的。能说不同的,是-a统计里对注释行的判断,“\”可以通过判断一行文字内是否存在得知,“/*……*/”,则可以先判断注释首部,让判断处于注释状态,再去寻找末尾。在功能判断的代码中因为单独判断一个元素(第一个元素arr[0]通常是“-c”“-w”之类的,这种不是路径的字符串无法给予file)会报错,我便把“-x”图形界面和“退出”两个判断放出switch。
3)递归调用。统计当前文件夹下符合的要求的文件,包括子文件。对于当前目录,由于arr[length-1]是最后一个元素,也就是你本来想要统计的文件路径。通过String类的substring(),LastIndexOf()方法分开父级文件夹名字和文件后缀名,用file.isFile()和file.isDirectory()等方法去判统计当前目录下的文件夹数量和名字、相同后缀名的文件数量和名字,这些数据被放在不同的字符串数组里面。对于子文件,我创建一个递归方法sonProcessor(),接受的数据时之前统计好的文件夹名字和统计文件的后缀名。相同的,递归方法对文件夹的数组里每一个文件夹路劲再一次进行统计,如果有,继续调用自己(这样做会让输出结果从最里面的文件夹开始统计),直到没有文件。最后是输出数据问题,我之前只写了一个文本输入流,却给了四个功能方法去调用,导致的结果是统计出来的数据会一直累计,一个3552字数的文件,在第二次统计达到8000多,最后我是分开了四个不同的文本输送流才解决了问题(我不知道是否有更好的方法,因为这么多文本输送肯定是不现实的)。
4)通配符的问题。我一开始会比较考虑要不要去写“正则表达式”(正则表达式其实比switch更具有判断性),但是做好了递归功能后我发现可以用String类的substring(),LastIndexOf()方法,对于通用符“*”,截取文件路径最后一次出现“”和“.”中间的地方,就是“*”通用符,因此在switch前可以加上一次判断,如果是“*”出现了,直接调用递归。
5)最后是图形界面,个人感觉是比较简单的,我设计了带有六个按钮和带有一个文本域的界面,分别对应-c,-w,-l,-a,综合和退出的功能,我先定义一个整数Type,功能按钮都给予能调用FileChooser的监听器,并且每个按钮都会改变Type的值,输出对应的统计内容。
四、设计与实现
主要分类只有三个,主类Wc,功能类Function_Directory以及界面类GUI_Frame。
主要调用流程:
五、测试运行
测试文件包括:三个java文件,一个txt文件,两个不相关文件夹、嵌套目录(文件内带有三个java文件,一个子文件夹又带有三个java文件)
Wc.java在CMD里面的运行界面:
基础功能与扩展功能-a:
递归调用-s与通用符“*”:
图形界面:
Wc.java
static boolean TRUE_OR_NOT = true;
public static void main(String[] args){
System.out.println("*-------------------WC统计-------------------*");
System.out.println("基础功能说明:");
System.out.println(" 统计字符数:-c [您要查找的文件路径]");
System.out.println(" 统计词数: -w [您要查找的文件路径]");
System.out.println(" 统计行数: -l [您要查找的文件路径]");
System.out.println("");
System.out.println("拓展功能说明:");
System.out.println(" 统计空行、代码行、注释行: -a [您要查找的文件路径]");
System.out.println(" 递归处理: -s [您要查找的文件路径]");
System.out.println("");
System.out.println("高级功能说明:");
System.out.println(" 图形化界面:-x");
System.out.println("");
System.out.println("若想退出程序请输入:退出");
System.out.println("*--------------------------------------------*");
System.out.println("请输入指令:");
while(TRUE_OR_NOT){
Scanner command = new Scanner(System.in);
String[] arr = command.nextLine().split("\s");
int len = arr.length;
Function_Directory FD = new Function_Directory();
FD.commandProcessor(arr,len,0,arr[arr.length-1]); //把数据都交给FD对象里面
}
}
GUI_Frame.java
private static final long serialVersionUID = 1L;
Toolkit kit = Toolkit.getDefaultToolkit();
Dimension screenSize = kit.getScreenSize();
static JTextArea textArea = new JTextArea();
private JPanel chooseBar = new JPanel();
private JButton count_c = new JButton("字符数");
private JButton count_w = new JButton("词数");
private JButton count_l = new JButton("行数");
private JButton count_a = new JButton("拓展功能");
private JButton count_all = new JButton("总结");
private JButton count_close = new JButton("关闭");
private Font buttonFont = new Font("宋体",Font.BOLD,25);
int charNum = 0;
int wordNum = 0;
int lineNum = 0;
int blankLineNum = 0;
int codeLineNum = 0;
int annotationLineNum = 0;
int Type = 0;
ArrayList<Integer> resultList = null;
GUI_Frame(){
setTitle("WC统计");
setSize(2*screenSize.width/4,3*screenSize.height/4);
setLocation(2*screenSize.width/4,screenSize.height/8);
initEventListeners();
add(chooseBar,BorderLayout.NORTH);
initChooseBar();
initTextArea();
}
private void initChooseBar(){ //顶部选项栏设置
chooseBar.add(count_c);
chooseBar.add(count_w);
chooseBar.add(count_l);
chooseBar.add(count_a);
chooseBar.add(count_all);
chooseBar.add(count_close);
count_c.setFont(buttonFont);
count_w.setFont(buttonFont);
count_l.setFont(buttonFont);
count_a.setFont(buttonFont);
count_all.setFont(buttonFont);
count_close.setFont(buttonFont);
}
private void initTextArea(){
textArea.setFont(new Font("宋体", Font.PLAIN, 30));
textArea.setMargin(new Insets(3,10,3,10));
textArea.setLineWrap(true);
textArea.setDragEnabled(true);
JScrollPane panel = new JScrollPane(textArea,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
getContentPane().add(panel, BorderLayout.CENTER);
}
private void initEventListeners(){
count_c.addActionListener( this::countC );
count_w.addActionListener( this::countW );
count_l.addActionListener( this::countL );
count_a.addActionListener( this::countA );
count_all.addActionListener( this::countAll );
count_close.addActionListener( this::countClose);
}
private void countC(ActionEvent event){
Type = 1;
CountStart();
}
private void countW(ActionEvent event){
Type = 2;
CountStart();
}
private void countL(ActionEvent event){
Type = 3;
CountStart();
}
private void countA(ActionEvent event){
Type = 4;
CountStart();
}
private void countAll(ActionEvent event){
Type = 5;
CountStart();
}
private void countClose(ActionEvent event){
Type = 0;
System.exit(0);
}
public void CountStart(){
JFileChooser chooser = new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
chooser.showDialog(new JLabel(), "选择要统计的文件");
File file = chooser.getSelectedFile();
try{
String encoding = "GBK";
InputStreamReader readFile = new InputStreamReader(new FileInputStream(file),encoding);
BufferedReader fileContent = new BufferedReader(readFile);
switch(Type){
case 1:
charNum = Function_Directory.charsCounter(fileContent);
textArea.append(file+": "+"字符数:" + charNum + " ");
break;
case 2:
wordNum = Function_Directory.wordsCounter(fileContent);
textArea.append(file+": 词数:" + wordNum+" ");
break;
case 3:
lineNum = Function_Directory.linesCounter(fileContent);
textArea.append(file+": 行数:" + lineNum+" ");
break;
case 4:
resultList = Function_Directory.Expand_Count(fileContent);
blankLineNum = resultList.get(0);
codeLineNum = resultList.get(1);
annotationLineNum = resultList.get(2);
textArea.append(file+": 空行数:" + blankLineNum + " 代码行数:"
+ codeLineNum + " 注释行数" + annotationLineNum+" ");
break;
case 5:
charNum = Function_Directory.charsCounter(fileContent);
wordNum = Function_Directory.wordsCounter(fileContent);
lineNum = Function_Directory.linesCounter(fileContent);
resultList = Function_Directory.Expand_Count(fileContent);
blankLineNum = resultList.get(0);
codeLineNum = resultList.get(1);
annotationLineNum = resultList.get(2);
textArea.append(file+": "+"字符数:" + charNum + " 词数:"
+ wordNum + " 行数:" + lineNum + " 空行数:" + blankLineNum + " 代码行数:"
+ codeLineNum + " 注释行数" + annotationLineNum+" ");
}
}catch(IOException e){
System.out.println("文件路径错误或者文件不支持喔~");
e.printStackTrace();
}
}
public static void main(String[] args) {
GUI_Frame GF = new GUI_Frame();
GF.setVisible(true);
}
Function_Directory.java
public void commandProcessor(String[] arr,int len,int start,String fileName){
if(arr[0].equals("-x")){ //由于只输入一个元素会报错,“-x”和“退出”先提前判断
GUI_Frame GF = new GUI_Frame(); //一个图形化界面
GF.setVisible(true);
} else if(arr[0].equals("退出")) { Wc.TRUE_OR_NOT = false;
} else{
try{
for(int i = start;i < len-1||i == 0;i++){
String fileUrl = arr[arr.length-1].substring(arr[arr.length-1].lastIndexOf("\")+1,arr[arr.length-1].lastIndexOf("."));
System.out.println(fileUrl);
if(fileUrl.equals("*")) recursiveProcessor(arr); //判断通用符号“*”
else{
String encoding = "GBK";
File file = new File(fileName);
InputStreamReader readFile = new InputStreamReader(new FileInputStream(file),encoding);
BufferedReader fileContent = new BufferedReader(readFile);
switch(arr[i]){
case "-c":
charsCounter(fileContent);
break;
case "-w":
wordsCounter(fileContent);
break;
case "-l":
linesCounter(fileContent);
break;
case "-a":
Expand_Count(fileContent);
break;
case "-s":
recursiveProcessor(arr);
break;
default:break;
}
}}
}catch(Exception e){
System.out.println(arr[0]+"不是功能指令喔~,看看是不是哪里输入错误了。");
e.printStackTrace();
}
}
}
static int charsCounter(BufferedReader fileContent) throws IOException{
String lineCount = null;
int charNum = 0;
while((lineCount = fileContent.readLine()) != null){
lineCount = lineCount.trim();
for(int i = 0;i < lineCount.length();i++){
char ch = lineCount.charAt(i);
if(ch != ' ' && ch != ' ' && ch != ' ')
charNum++;
}
}
System.out.println("字符数:" + charNum);
return charNum;
}
static int wordsCounter(BufferedReader fileContent) throws IOException{
String REGEX = "[a-zA-Z]+\b"; //判断词的正则表达式
String lineCount = null;
int wordNum = 0;
Pattern pattern = Pattern.compile(REGEX);
while((lineCount = fileContent.readLine()) != null){
lineCount = lineCount.trim();
Matcher matcher = pattern.matcher(lineCount);
while(matcher.find()){
wordNum++;
}
}
System.out.println("词数:" + wordNum);
return wordNum;
}
static int linesCounter(BufferedReader fileContent) throws IOException{
int lineNum = 0;
String lineCount = null;
while((lineCount = fileContent.readLine()) != null){ lineNum++; }
System.out.println("行数:" + lineNum);
return lineNum;
}
static ArrayList<Integer> Expand_Count(BufferedReader fileContent)throws IOException{
String lineCount = null;
ArrayList<Integer> resultList = new ArrayList<Integer>();
boolean isComment = false;
int codeLineNum = 0;
int blankLineNum = 0;
int annotationLineNum = 0;
while((lineCount = fileContent.readLine()) != null){ //对注释行的判断
if(lineCount.contains("/*")){
annotationLineNum++;
isComment = true;
} else if(isComment){
annotationLineNum++;
if(lineCount.contains("*/")){ isComment = false; }
} else if(lineCount.contains("//")){
annotationLineNum++;
} else if(lineCount.trim().length() > 1){
codeLineNum++;
} else{
blankLineNum++;
}
}
System.out.println("空行数:" + blankLineNum);
System.out.println("代码行数:" + codeLineNum);
System.out.println("注释行数:" + annotationLineNum);
resultList.add(blankLineNum);
resultList.add(codeLineNum);
resultList.add(annotationLineNum);
return resultList;
}
public void recursiveProcessor(String[] arr) throws IOException{
int LA = arr.length;
int n = 0;
String fileUrl = arr[LA-1].substring(0,arr[LA-1].lastIndexOf("\")); //找到文件路径最先出现“”的位置
String fileEnd = arr[LA-1].substring(arr[LA-1].lastIndexOf(".")); //找到文件路径最先出现“.”的位置
List<File> fileList = new ArrayList<File>();
File file = new File(fileUrl);
File[] files = file.listFiles();
String[] names = file.list();
String[] CompletNames = null;
for (File f : files) {
if (f.isDirectory()){ n++; }
}
CompletNames = new String[n];
n = 0;
if (files == null) {}
for (File f : files) {
if (f.isFile()&&f.getName().endsWith(fileEnd)) { fileList.add(f); }
if (f.isDirectory()){
CompletNames[n] = arr[LA-1].substring(0,arr[LA-1].lastIndexOf("\"))+"\"+f.getName();
System.out.println(CompletNames[n]);
n++;
}
}
for (File f1 : fileList) {
System.out.println(fileUrl+"\"+f1.getName());
String encoding = "GBK";
InputStreamReader rC = new InputStreamReader(new FileInputStream(f1),encoding);
InputStreamReader rW = new InputStreamReader(new FileInputStream(f1),encoding);
InputStreamReader rL = new InputStreamReader(new FileInputStream(f1),encoding);
InputStreamReader rA = new InputStreamReader(new FileInputStream(f1),encoding);
BufferedReader C = new BufferedReader(rC);
BufferedReader W = new BufferedReader(rW);
BufferedReader L = new BufferedReader(rL);
BufferedReader A = new BufferedReader(rA);
charsCounter(C);
wordsCounter(W);
linesCounter(L);
Expand_Count(A);
C.close(); //我试过只写一个文本输入流,统计结果会累积,数据错误
W.close(); //由于写入太多输入流,希望想找到更好的办法
L.close();
A.close();
}
System.out.println("*-----------------------------*");
if (CompletNames!=null) sonProcessor(CompletNames,fileEnd);
}
String[] N = Names; //寻找子文件,并把子文件里面符合要求的文件统计出来
int LengthA = N.length; //这里我运用了递归,但是发现是先递归到最里层才开始统计
for(int i=0;i<Names.length;i++){
int n = 0;
String fileUrl = N[i];
String fileEnd = End;
List<File> fileList = new ArrayList<File>();
File file = new File(fileUrl);
File[] files = file.listFiles();
String[] names = file.list();
String[] CompletNames = null;
for (File f : files) {
if (f.isDirectory()){ n++; }
}
CompletNames = new String[n];
n =0;
if (files == null) {}
for (File f : files) {
if (f.isFile()&&f.getName().endsWith(fileEnd)) { fileList.add(f); }
if (f.isDirectory()){
CompletNames[n] = N[i]+"\"+f.getName();
System.out.println(CompletNames[n]);
n++;
}
}
if (CompletNames!=null) sonProcessor(CompletNames,fileEnd);
for (File f1 : fileList) {
System.out.println(N[i]+"\"+f1.getName());
String encoding = "GBK";
InputStreamReader rC = new InputStreamReader(new FileInputStream(f1),encoding);
InputStreamReader rW = new InputStreamReader(new FileInputStream(f1),encoding);
InputStreamReader rL = new InputStreamReader(new FileInputStream(f1),encoding);
InputStreamReader rA = new InputStreamReader(new FileInputStream(f1),encoding);
BufferedReader C = new BufferedReader(rC);
BufferedReader W = new BufferedReader(rW);
BufferedReader L = new BufferedReader(rL);
BufferedReader A = new BufferedReader(rA);
charsCounter(C);
wordsCounter(W);
linesCounter(L);
Expand_Count(A);
C.close();
W.close();
L.close();
A.close();
System.out.println("*-----------------------------*");
}
}
}
六、项目总结:
这次项目让我感受最深的是做这个项目的计划,先给自己一个规划,做出要想做的框架流程,想好自己应该干嘛,用多少时间,应该怎么去测试,以前的课设我是没有注意到这些的,这也让我看到做一个项目的严谨性。之前的java程序我都是通过设计图形化界面去运行的,没有像这次在命令行里面运行,学会了如何在命令行编译与运行java文件。递归调用的实现也是我印象最深刻的,文件夹和文件的路径由于循环让我自己都搞晕了,不断调试,不断找准切分位置,不断有报错,文件夹数组长度不能过长,路径不能错误,有时数据会卡住循环进行。递归的实现是我用时最多的部分。但是因为想去搞懂,从网上找资料,询问同学,学到不少,也能想出适合自己的方法。现在是个人项目,我期待在后面的项目能收获更多。