首页 > 编程语言 > 详细

文本统计工具(Java)

时间:2020-03-20 01:13:09      阅读:76      评论:0      收藏:0      [点我收藏+]

WordCount

GitHub地址:https://github.com/soperfect/SE_work2_TextCounting

一、项目相关要求

  1. wc.exe 是一个常见的工具,它能统计文本文件的字符数、单词数和行数。这个项目要求写一个命令行程序,模仿已有wc.exe 的功能,并加以扩充,给出某程序设计语言源文件的字符数、单词数和行数。

  2. 实现一个统计程序,它能正确统计程序文件中的字符数、单词数、行数,以及还具备其他扩展功能,并能够快速地处理多个文件。程序处理用户需求的模式为:wc.exe [parameter] [file_name]

  3. 基本功能

    wc.exe -c file.c    //返回文件 file.c 的字符数
    wc.exe -w file.c    //返回文件 file.c 的词的数目  
    wc.exe -l file.c    //返回文件 file.c 的行数
  4. 扩展功能

    -s   递归处理目录下符合条件的文件。
    -a   返回更复杂的数据(代码行 / 空行 / 注释行)。
    ?
    空行:本行全部是空格或格式控制字符,如果包括代码,则只有不超过一个可显示的字符,例如“{”。
    代码行:本行包括多于一个字符的代码。
    注释行:本行不是代码行,并且本行包括注释。一个有趣的例子是有些程序员会在单字符后面加注释:
      } //注释
    在这种情况下,这一行属于注释行。
    特殊情况,如        return; } //注释
    既算是代码行也算是注释行。
    [file_name]: 文件或目录名,可以处理一般通配符。
  5. 高级功能

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

二、解题思路

  1. 算上扩展功能和高级功能,需要实现的功能如下

    • 统计字符数:每次读取文本的一行,统计这一行的字符数,然后累加

    • 统计单词数:将文中的中文以及其他符号用" "取代,然后用空格分割取代后的字符串,然后分割后得到的数组转换成集合,使用集合的remove的功能去除集合中的" ",去除后集合的长度就是词的数目。其中使用的guava的Multiset

    • 统计行数:一次读取一行,累加行数

    • 递归处理:判断路径是不是目录,不是则报错,是的话把目录下的文件添加到集合里,目录下的子目录再递归处理,最后返回文件路径的集合。

    • 返回复杂的数据:按所要求的进行判断累加。

    • 通配符匹配:只能匹配*?,如果只有单独一个?则会报错,匹配后把符合条件的文件名添加到集合中,通配符中含有其他的符号或者没有这两个字符,最后返回空集合。

    • 图形界面:用Java提供的Swing组件进行绘图
  2. 拿到题目后查找了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("?") && names[0].length() == character[0].length())
                               result.add(string);
                      }
                       //文件名含有?
                       else if(character[0].contains("?")){
                           if(names[0].length() == character[0].length() && names[1].equals(character[1])){
                               result.add(string);
                          }
                      }
                  }
              }else
                   result = list;
          }
           return result;
      }

五、测试运行

jar包打包成exe文件后进行测试

  1. 基本功能

    技术分享图片

    技术分享图片

    技术分享图片

    技术分享图片

     

  2. 扩展功能,新增两个功能同时可以匹配通配符

    • 遍历目录下的文件

    技术分享图片

    • 递归遍历目录及子目录,同时使用通配符

    技术分享图片

    技术分享图片

    • 错误处理(1、单独使用参数“-s”,2、对文件使用参数“-s”,3、单独输入参数或者单独输入文件,4、输入错误的路径)

    技术分享图片

    技术分享图片

  3. 高级功能,选取一个文件或者目录进行统计

    技术分享图片

    技术分享图片

  4. 单元测试以及代码覆盖率,对WcUtils中的功能函数进行单元测试

    技术分享图片

 

六、PSP

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

七、项目小结

  1. 开始个人项目后,在需求分析上遇到了一点小小的困难,纠结于是否有标准,最后明确了是我在进行需求分析,而需求分析的对象不再是客户而是我本身,所以根据自己的需要进行需求分析。

  2. 开发的时候,整个个人项目中觉得最费时间的地方应该是通配符匹配的功能,目前只能匹配*?,考虑每一种可能的情况判断起来比较繁琐。

  3. 整体上项目所要求的功能包括扩展的以及图形界面都实现了,但是还是存在不足的地方的,为保证每一个功能函数都是独立的且可重复使用的,没有在函数中输出结果,而是返回结果后在main函数中再进行输出,导致main函数比较冗长,应该进行进一步的封装。

  4. 整个项目花费的时间比预估的要多上很多

文本统计工具(Java)

原文:https://www.cnblogs.com/yade/p/12528414.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!