JAVA实现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   返回更复杂的数据(代码行 / 空行 / 注释行)。(实现)

  • 高级功能:

  -x 参数。这个参数单独使用。如果命令行有这个参数,则程序会显示图形界面,用户可以通过界面选取单个文件,程序就会显示文件的字符数、行数等全部统计信息。

  (未实现)

  需求举例:
    wc.exe -s -a *.c (实现)

    返回当前目录及子目录中所有*.c 文件的代码行数、空行数、注释行数。

扫描二维码关注公众号,回复: 3100375 查看本文章

 Github项目地址:https://github.com/kvhong/JAVA-to-bulid-wc.exe

 解题思路

主函数思路:通过输入的命令来判断执行各个功能函数,将输入的命令分割为两部分,第一部分是指令,第二部分是文件路径,用指令来调用各个功能函数,文件路径则作为参数输入到各个功能函数中实现功能。

功能函数的共同部分:通过传入的文件路径并用Buffer流读取文件内容,对文件内容进行相应统计然后输出结果,需实现文件不存在或者路径输入错误提示。

词数统计函数:需要去除空行、各种符号,将独立的词统计出来;

字符统计函数:需要去除空行、空格,将其余的内容的每一个单元都示为一个字符统计出来;

行数统计函数:文件中全部行数除结尾空行不算外其他都统计;

空行:TAB、空格、回车形成的空行都算入;

注释行:单独存在//、/*、*/的算入注释行;

代码行:除了空行、注释行外属于代码行;

帮助:将各种命令列出,以更好的帮助使用;

 遇到的问题及解决方法

一开始不知道如何对词、字符进行有效的分割,然后在网上学习到可以用正则表达式进行分割:使用下面语句将数字和和中英文标点符号和中文都替换成空格,以通过空格分割出各个词。

str.replaceAll("[\\p{Nd}\\u9fa5-\\uffe5\\p{Punct}\\s&&[^-]]", " ");

使用下面的正则匹配器匹配注释行和空行

Pattern nodeLinePattern = Pattern.compile("((//)|(/\\*+)|((^\\s)*\\*)|((^\\s)*\\*+/))+", 
                        Pattern.MULTILINE + Pattern.DOTALL);    // 注释匹配器(匹配单行、多行、文档注释)
Pattern spaceLinePattern = Pattern.compile("^\\s*$");    // 空白行匹配器(匹配回车、tab键、空格)

 设计及代码

 该程序通过一个主函数文件和一个方法函数文件组成。主函数文件用于输入参数并判断所输入的各种操作以调用方法函数。方法函数用于实现各种功能:

文件词数统计函数:getwordnumber()  命令:-w  文件路径(须包含完整路径)

//文件词数统计函数
void getwordnumber(String filename) throws IOException {
        String ret = null;
        int num=0;
        String[] strword = null;
        File file = new File(filename);
        if(file.exists()) {
            //读取文件
            FileReader fr = new FileReader(filename);
            br = new BufferedReader(fr);
            String line = null;
            StringBuffer sbf = new StringBuffer();
            while((line=br.readLine())!= null) {
                sbf.append(line);
                String str = sbf.toString();
                //正则表达式替换数字和和中英文标点符号和中文
                str = str.replaceAll("[\\p{Nd}\\u9fa5-\\uffe5\\p{Punct}\\s&&[^-]]", " ");
                //按空格将内容分割
                strword = str.split("\\s+");
                num=strword.length;
            }
            ret= "该程序文件中单词数为:"+num;
            br.close();
            fr.close();
       System.out.println(ret); }
else { System.out.println("文件不存在,请重新输入文件!"); } }

文件字符数统计函数:getCharacternumber()  命令:-c  文件路径(须包含完整路径)

  //文件字符统计函数
    void getCharacternumber(String filename) throws IOException {
        String ret = null;
        int number = 0;
        String[] strword = null;
        File file = new File(filename);
        if(file.exists()) {
            //读取文件
            FileReader fr = new FileReader(filename);
            br = new BufferedReader(fr);
            String line = null;
            String str=null;
            StringBuffer sbf = new StringBuffer();
            while((line=br.readLine())!= null) {
                sbf.append(line);
                str = sbf.toString();
                strword = str.split("\\s+");
            }
       //通过正则匹配器匹配一个或多个字母和数字
            for(int i=0;i<strword.length;i++) {
                Pattern pattern = Pattern.compile("[0-9a-zA-Z]*");
                Matcher matcher = pattern.matcher(strword[i]);
                if(matcher.find()) {
                    number+=matcher.regionEnd();
                }
            }
            ret = "该程序文件中的字符数为:"+number;
            br.close();
            fr.close();
       System.out.println(ret);
        }else {
            System.out.println("文件不存在,请重新输入文件!");
        }
    }

文件行数统计函数:getlinenumber()  命令:-l  文件路径(须包含完整路径)

  //文件行数统计函数
    void getlinenumber(String filename) throws IOException {
        String ret = null;
        int linenum = 0;
        File file = new File(filename);
        if(file.exists()) {
            //读取文件
            FileReader fr = new FileReader(filename);
            //读取文件行数
            LineNumberReader lnr = new LineNumberReader(fr);
            while(lnr.readLine()!= null) {
                linenum=lnr.getLineNumber();
            }
            ret = "该程序文件中的行数为:"+linenum;
            lnr.close();
            fr.close();
       System.out.println(ret); }
else { System.out.println("文件不存在,请重新输入文件!"); } }

统计文件或者文件夹中程序文件的空行、代码行、注释行,有递归文件目录功能的函数,将递归功能一同实现在此函数中:diffline()  命令:-a 文件路径或文件夹路径/-s-a 文件路径或文件夹路径

    //统计文件或者文件夹中程序文件的空行、代码行、注释行,有递归文件目录功能
        void diffline(File file) throws FileNotFoundException {
            int spaceline = 0;
            int nodeline = 0;
            int codeline = 0;
            if (file == null || !file.exists()) 
                throw new FileNotFoundException(file + ",文件不存在!");
     
            if (file.isDirectory()) {
                File[] files = file.listFiles(new FileFilter() {
                    public boolean accept(File pathname) {
                        return pathname.getName().endsWith(".java")|| pathname.isDirectory()||pathname.getName().endsWith(".c")||pathname.getName().endsWith(".cpp")  ;
                    }
                });
                //递归文件目录
                for (File target : files) {
                    diffline(target);
                }
            } else {
                System.out.println("文件名:"+file.getAbsolutePath());
                BufferedReader bufr = null;
                try {
                    // 将指定路径的文件与字符流绑定
                    bufr = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
                } catch (FileNotFoundException e) {
                    throw new FileNotFoundException(file + ",文件不存在!" + e);
                }
     
                // 定义匹配每一行的正则匹配器
                Pattern nodeLinePattern = Pattern.compile("((//)|(/\\*+)|((^\\s)*\\*)|((^\\s)*\\*+/))+", 
                        Pattern.MULTILINE + Pattern.DOTALL);    // 注释匹配器(匹配单行、多行、文档注释)
     
                Pattern spaceLinePattern = Pattern.compile("^\\s*$");    // 空白行匹配器(匹配回车、tab键、空格)
                
                // 遍历文件中的每一行,并根据正则匹配的结果记录每一行匹配的结果
                String line = null;
                try {
                    while((line = bufr.readLine()) != null) {
                        if (nodeLinePattern.matcher(line).find()) {
                            nodeline ++;
                        }else if (spaceLinePattern.matcher(line).find()) {
                            spaceline ++;
                        }else{
                            codeline ++;
                        } 
                    }
                    System.out.println("空行:"+spaceline);
                    System.out.println("代码行:"+codeline);
                    System.out.println("注释行:"+nodeline);
                } catch (IOException e) {
                    throw new RuntimeException("读取文件失败!" + e);
                } finally {
                    try {
                        bufr.close();    // 关闭文件输入流并释放系统资源
                    } catch (IOException e) {
                        throw new RuntimeException("关闭文件输入流失败!");
                    }
                }
            }
        }

帮助函数:help()  命令:?

    //帮助函数
        void help(){
            System.out.println("-c 文件(须包含文件完整路径)   统计程序文件中的字符数");
            System.out.println("-w 文件(须包含文件完整路径)   统计程序文件中的单词数");
            System.out.println("-l 文件(须包含文件完整路径)   统计程序文件中的行数");
            System.out.println("-a 文件(须包含文件完整路径)   统计程序文件中的空行数、代码行数、注释行数");
            System.out.println("-s-a 文件路径或者文件夹路径        递归统计程序文件中的空行数、代码行数、注释行数");
       System.out.println("? 帮助"); }

主函数:wctest.java

public class wctest{
    
    private static Scanner scanner;

    public static void main(String[] args) throws IOException{
        String str = null;
        wcfunction wcf = new wcfunction();
        //循环询问命令输入
        while(true) {
            System.out.print("请输入命令:");
            //命令输入
            scanner = new Scanner(System.in);
            if(scanner.hasNext()) {
            str=scanner.nextLine();
            }
            //分割命令,第一个作为判断第二个为文件路径
            String[] strword = str.split(" ");
            if(strword[0].equals("-c")) {
                wcf.getCharacternumber(strword[1]);
            }else if(strword[0].equals("-w")) {
                wcf.getwordnumber(strword[1]);
            }else if(strword[0].equals("-l")) {
                wcf.getlinenumber(strword[1]);
            }else if(strword[0].equals("-a")) {
                File file = new File(strword[1]);
                wcf.diffline(file);
            }else if(strword[0].equals("-s-a")) {
                File file = new File(strword[1]);
                wcf.diffline(file);
            }else if(strword[0].equals("?")) {
                wcf.help();
            }
        }
    }
}

测试

已将程序打包成wc.exe文件,放在Github项目中。可直接打开exe文件输入命令和文件路径直接使用,也可使用CMD命令行运行wc.exe再输入命令和文件路径使用。

CMD命令行使用:

exe直接使用:

代码覆盖率


代码覆盖率:91.8%

PSP

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 45 60
· Estimate · 估计这个任务需要多少时间 45 60
Development 开发 1290 1540
· Analysis · 需求分析 (包括学习新技术) 80 120
· Design Spec · 生成设计文档 40 60
· Design Review · 设计复审 (和同事审核设计文档) 40 40
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 30 30
· Design · 具体设计 120 160
· Coding · 具体编码 780 920
· Code Review · 代码复审 80 90
· Test · 测试(自我测试,修改代码,提交修改) 120 120
Reporting 报告 240 380
· Test Report · 测试报告 120 220
· Size Measurement · 计算工作量 30 40
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 90 120
  合计 1575 1980

 

总结

先是从课堂上更加意识到制定设计流程的重要性,了解到计划的设计不仅仅只存在于流程,对时间的规划也是非常重要的。在编写代码过程中实质遇到的困难是在如何实现词数的统计、正则表达式以及对文件目录的递归实现。最后通过学习正则表达式实现了对词数的统计,也进一步学习了正则表达式这一较为陌生的概念。也学会了一些新的插件和软件的使用:Eclipse的EclEmma插件用于代码覆盖率的统计;Git Bash软件对本地项目上传到Github使用;exe4j软件对JAVA项目进行EXE文件的打包使用。这次的项目作业对我来说是受益匪浅,对以后的项目实行有很大的帮助。

猜你喜欢

转载自www.cnblogs.com/K-mengmengpi/p/9614405.html