zoukankan      html  css  js  c++  java
  • WordCount程序(Java)

     

    Github项目地址:https://github.com/softwareCQT/web_camp/tree/master/wordCount

    一、题目描述

    • 实现一个简单而完整的软件工具(源程序特征统计程序)。

    • 进行单元测试、回归测试、效能测试,在实现上述程序的过程中使用相关的工具。

    • 进行个人软件过程(PSP)的实践,逐步记录自己在每个软件工程环节花费的时间。


    二、WC 项目要求

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

    • 实现一个统计程序,它能正确统计程序文件中的字符数、单词数、行数,以及还具备其他扩展功能,并能够快速地处理多个文件。

    • 具体功能要求:程序处理用户需求的模式为:wc.exe [parameter] [file_name]


    三、核心代码

    • 文件处理(包括处理通配符*?和-s递归)

      /**
      * @author chenqiting
      */
      public class FileUtil {

         /***
          * 判断文件是否合法 且 处理通配符并返回文件列表
          * @return List<File>
          */
         public static List<File> accessRegx(String fileName){
             if (fileName.contains(CommandConstants.UNIVERSAL_CHAR_ONE)
                     || fileName.contains(CommandConstants.UNIVERSAL_CHAR_TWO)) {
               //判断是否存在通配符,统一换掉参数
                 fileName = fileName.
                         replace(CommandConstants.UNIVERSAL_CHAR_TWO, CommandConstants.UNIVERSAL_CHAR_ONE);
                 //如果是绝对路径,获取绝对路径的前半段,即获取到*号之前的路径
                 int index = fileName.indexOf("*");
                 //标志文件是否在文件后缀加的通配符
                 boolean flag = (index == fileName.length() - 1);
                 String parentDirectory;
                 String childFileName;
                 //如果是文件类型通配符,父路径需要重新处理
                 if (flag) {
                     index = fileName.lastIndexOf("\");
                     index = index == -1 ? 0 : index;
                }
                 parentDirectory = fileName.substring(0, index);
                 childFileName = fileName.substring(index);
                 //系统路径匹配器
                 PathMatcher pathMatcher;
                 File file;
                 //空字符串表示需要当前路径匹配
                 if ("".equals(parentDirectory)){
                     file = new File(".");
                     String string = file.getAbsolutePath().replace(".", "").replace("\", "\\");

                     file = new File(string);
                     pathMatcher = FileSystems.getDefault().
                             getPathMatcher("regex:" + string + "\\" + childFileName);
                }else {
                     parentDirectory = parentDirectory.replace("\", "\\");
                     file = new File(parentDirectory);
                     //在parentDirectory目录下进行遍历
                     pathMatcher = FileSystems.getDefault().
                             getPathMatcher("glob:" + parentDirectory + childFileName);
                }
                 return stackForFile(file, pathMatcher);
            }else {
                 File file = new File(fileName);
                 //判断文件是否存在
                 if (file.exists()){
                     if (file.isDirectory()){
                         System.out.println(file.getName() + "不是文件,请重新输入");
                    }
                }else {
                     System.out.println("找不到该文件");
                }
                 ArrayList<File> arrayList = new ArrayList<>(1);
                 arrayList.add(file);
                 return arrayList;
            }
        }

         /***
          * 处理当前目录下的所有符合的文件
          * @return 文件的集合
          */
         public static List<File> getBlowFile(String fileName){
             String newFileName = fileName.
                     replace(CommandConstants.UNIVERSAL_CHAR_TWO, CommandConstants.UNIVERSAL_CHAR_ONE);
             //路径匹配器
             PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:" + "**/" + newFileName);
             return stackForFile(new File("."), pathMatcher);
        }

         /***
          * 把当前文件夹下的文件放进栈
          * @param file
          * @param stringDeque
          */
         private static void addToStack(File file, Queue<File> stringDeque) {
             File[] string = file.listFiles();
             if (!Objects.isNull(string)) {
                 Collections.addAll(stringDeque, string);
            }
        }

         /***
          * 递归匹配查找函数
          * @param parent 父目录
          * @param pathMatcher 匹配器
          * @return 文件
          */
         private static List<File> stackForFile(File parent, PathMatcher pathMatcher){
             //文件不存在
             if (!parent.exists()) {
                 return null;
            }
             ArrayDeque<File> stringDeque = new ArrayDeque<>();
             addToStack(parent, stringDeque);
             //创建结果集合
             List<File> strings = new ArrayList<>();
             //用栈去处理文件
             while (!stringDeque.isEmpty()) {
                 File newFile = stringDeque.pollLast();

                 if (newFile.isDirectory()) {
                     addToStack(newFile, stringDeque);
                }else {
                     if (pathMatcher.matches(newFile.toPath())){
                         strings.add(newFile);
                    }
                }
            }
             return strings;
        }
    • 获取文件流中的数据,用BufferedReader读取流,然后转换流为List

      /***
      * 打开文件流并执行命令操作
      * @param files 文件
      * @param commandString 命令字符串
      * @throws NullPointerException 空指针防止有未知命令出现
      */
      private static void invoke(List<File> files, List<String> commandString) throws NullPointerException{
         //判空处理
         if (Objects.isNull(files) || files.isEmpty()) {
             System.out.println("文件参数使用错误或目录下没有匹配文件");
             return;
        }
         //对文件进行命令操作
         files.forEach(file -> {
             try (BufferedReader bufferedReader = new BufferedReader(new FileReader(file))){
                 System.out.println("文件名称:" + file.getPath());
                 List<String> stringList = bufferedReader.lines().collect(Collectors.toList());
                 for (String string : commandString) {
                     BaseCommand baseCommand =  CommandFactory.getValue(string);
                     baseCommand.fileLineList(stringList).invoke();

                }
            } catch (IOException e) {
                 System.out.println("文件错误");
            }
        });
      }
    • -c 命令处理

      /**
      * @author chenqiting
      */
      public class AllBaseCommand extends BaseCommand<AllBaseCommand> {

         @Override
         public void invoke() throws IOException {
             //分别统计注释行、代码行、空行
             int descriptionLine = 0;
             int codeLine = 0;
             int nullLine = 0;
             //标注多行注释
             boolean flag = false;
             //用来引用处理过的string
             String stringBuffer;
             for (String string : fileLineList){
                 //去掉所有空白字符
                 stringBuffer = string.replaceAll("\s+", "");
                 //先判断flag是否为真,优先处理
                 if (flag){
                     if (string.endsWith("*/")){
                         flag = false;
                    }
                     descriptionLine++;
                     continue;
                }
                 //空行
                 if ("".equals(string)){
                     nullLine++;
                } else if (stringBuffer.startsWith("/*") && stringBuffer.endsWith("*/")){
                     //同行注释同行结束,直接相加
                     descriptionLine++;
                } else if (stringBuffer.startsWith("/*") && !stringBuffer.endsWith("*/")){
                     flag = true;
                     descriptionLine++;
                } else if (stringBuffer.matches("^\S(//)")){
                     //单字符后存在的注释情况//
                     descriptionLine++;
                } else {
                     //其余全为代码行
                     codeLine++;
                }
            }
             System.out.println("空行:" + nullLine);
             System.out.println("注释行:" + descriptionLine);
             System.out.println("代码行:" + codeLine);
        }
      }
    • -l命令处理

      /**
      * @author chenqiting
      */
      public class AllBaseCommand extends BaseCommand<AllBaseCommand> {

         @Override
         public void invoke() throws IOException {
             //分别统计注释行、代码行、空行
             int descriptionLine = 0;
             int codeLine = 0;
             int nullLine = 0;
             //标注多行注释
             boolean flag = false;
             //用来引用处理过的string
             String stringBuffer;
             for (String string : fileLineList){
                 //去掉所有空白字符
                 stringBuffer = string.replaceAll("\s+", "");
                 //先判断flag是否为真,优先处理
                 if (flag){
                     if (string.endsWith("*/")){
                         flag = false;
                    }
                     descriptionLine++;
                     continue;
                }
                 //空行
                 if ("".equals(string)){
                     nullLine++;
                } else if (stringBuffer.startsWith("/*") && stringBuffer.endsWith("*/")){
                     //同行注释同行结束,直接相加
                     descriptionLine++;
                } else if (stringBuffer.startsWith("/*") && !stringBuffer.endsWith("*/")){
                     flag = true;
                     descriptionLine++;
                } else if (stringBuffer.matches("^\S(//)")){
                     //单字符后存在的注释情况//
                     descriptionLine++;
                } else {
                     //其余全为代码行
                     codeLine++;
                }
            }
             System.out.println("空行:" + nullLine);
             System.out.println("注释行:" + descriptionLine);
             System.out.println("代码行:" + codeLine);
        }
      }
    • -l命令处理

      /**
      * @author chenqiting
      */
      public class LineBaseCommand extends BaseCommand<LineBaseCommand> {
         @Override
         public void invoke() throws IOException {
             System.out.println("文件行数:" + fileLineList.size());
        }
      }
    • -a命令处理

       @Override
         public void invoke() throws IOException {
             //分别统计注释行、代码行、空行
             int descriptionLine = 0;
             int codeLine = 0;
             int nullLine = 0;
             //标注多行注释
             boolean flag = false;
             //用来引用处理过的string
             String stringBuffer;
             for (String string : fileLineList){
                 //去掉所有空白字符
                 stringBuffer = string.replaceAll("\s+", "");
                 //先判断flag是否为真,优先处理
                 if (flag){
                     if (string.endsWith("*/")){
                         flag = false;
                    }
                     descriptionLine++;
                     continue;
                }
                 //空行
                 if ("".equals(string)){
                     nullLine++;
                } else if (stringBuffer.startsWith("/*") && stringBuffer.endsWith("*/")){
                     //同行注释同行结束,直接相加
                     descriptionLine++;
                } else if (stringBuffer.startsWith("/*") && !stringBuffer.endsWith("*/")){
                     flag = true;
                     descriptionLine++;
                } else if (stringBuffer.matches("^\S(//)")){
                     //单字符后存在的注释情况//
                     descriptionLine++;
                } else {
                     //其余全为代码行
                     codeLine++;
                }
            }
             System.out.println("空行:" + nullLine);
             System.out.println("注释行:" + descriptionLine);
             System.out.println("代码行:" + codeLine);
        }
      }
    • 主函数调用,工厂方法

      public static void main(String[] args){
         //获取参数
         List<String> commandString = util.ArgsUtil.getCommand(args);
         String fileName = util.ArgsUtil.getFileName(args);

         try {
             //验证参数是否存在问题
             if (!commandString.isEmpty() && Objects.nonNull(fileName)) {
                 //判断是否存在-s递归
                 boolean flag = commandString.contains(CommandConstants.COUNT_SOME_FILE);
                 List<File> files;
                 //递归则获取文件目录
                 if (flag) {
                     //递归获取当前路径
                      files = FileUtil.getBlowFile(fileName);
                     // 因为-s命令比其他命令优先级高,且作用不同,所以要提前剔除
                     commandString.remove(CommandConstants.COUNT_SOME_FILE);
                } else {
                     //TODO 处理通配符的问题
                      files = FileUtil.accessRegx(fileName);
                }
                 invoke(files, commandString);
            } else if (commandString.size() == 1 && commandString.get(0).equals(CommandConstants.COUNT_HELP) ){
                 //需要对参数进行判断,然后实现CountHelp
                 CommandFactory.getValue(CommandConstants.COUNT_HELP).invoke();
            } else {
                 //参数错误
                 CommandFactory.getValue(CommandConstants.COUNT_ERROR).invoke();
            }
        } catch (NullPointerException e) {
             //对未知参数进行捕获
             System.out.println("命令出现未知参数");
        } catch (Exception e){
             System.out.println("系统错误");
        }
      }

      四、项目测试

      java并没有不可以直接实现exe程序,需要使用工具转换,且与wc.exe文件相协同的文件目录下必须存在jre

       

      


    五、PSP表格

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

    六、总结

    学习了一下Java对正则表达式的PatternAPI的支持,以及正则表达式的内容。整体来说Java可以用BufferedReader流直接通过Stream流来转换成集合存储每一行,导致文件内容可重用,而不用持续地进行IO。编码过程中也在思考设计模式可以在里面充当什么角色,所以根据命令的不同,我使用了工厂模式,程序可扩展性算中等。

  • 相关阅读:
    天心阁漫步
    大美湘江
    easyui tree基本操作
    盗墓笔记
    半年了
    文件上传控件值发生变化后自动提交表单
    宁静的夏夜
    今天您给别人让座,将来别人给您让座
    优先队列priority_queue 用法详解
    POJ2387
  • 原文地址:https://www.cnblogs.com/chenqiting/p/12554114.html
Copyright © 2011-2022 走看看