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.1 Personal 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。编码过程中也在思考设计模式可以在里面充当什么角色,所以根据命令的不同,我使用了工厂模式,程序可扩展性算中等。

WordCount程序(Java)的更多相关文章

  1. Spark练习之通过Spark Streaming实时计算wordcount程序

    Spark练习之通过Spark Streaming实时计算wordcount程序 Java版本 Scala版本 pom.xml Java版本 import org.apache.spark.Spark ...

  2. 编写Spark的WordCount程序并提交到集群运行[含scala和java两个版本]

    编写Spark的WordCount程序并提交到集群运行[含scala和java两个版本] 1. 开发环境 Jdk 1.7.0_72 Maven 3.2.1 Scala 2.10.6 Spark 1.6 ...

  3. 将java开发的wordcount程序提交到spark集群上运行

    今天来分享下将java开发的wordcount程序提交到spark集群上运行的步骤. 第一个步骤之前,先上传文本文件,spark.txt,然用命令hadoop fs -put spark.txt /s ...

  4. 大数据之路week07--day03(Hadoop深入理解,JAVA代码编写WordCount程序,以及扩展升级)

    什么是MapReduce 你想数出一摞牌中有多少张黑桃.直观方式是一张一张检查并且数出有多少张是黑桃. MapReduce方法则是: 1.给在座的所有玩家中分配这摞牌 2.让每个玩家数自己手中的牌有几 ...

  5. Hadoop入门实践之从WordCount程序说起

    这段时间需要学习Hadoop了,以前一直听说Hadoop,但是从来没有研究过,这几天粗略看完了<Hadoop实战>这本书,对Hadoop编程有了大致的了解.接下来就是多看多写了.以Hado ...

  6. [转] 用SBT编译Spark的WordCount程序

    问题导读: 1.什么是sbt? 2.sbt项目环境如何建立? 3.如何使用sbt编译打包scala? [sbt介绍 sbt是一个代码编译工具,是scala界的mvn,可以编译scala,java等,需 ...

  7. WordCount程序代码解

    package com.bigdata.hadoop.wordcount; import java.io.IOException; import org.apache.hadoop.conf.Conf ...

  8. Hadoop集群测试wordcount程序

    一.集群环境搭好了,我们来测试一下吧 1.在java下创建一个wordcount文件夹:mkdir wordcount 2.在此文件夹下创建两个文件,比如file1.txt和file2.txt 在fi ...

  9. Eclipse环境搭建并且运行wordcount程序

    一.安装Hadoop插件 1. 所需环境  hadoop2.0伪分布式环境平台正常运行 所需压缩包:eclipse-jee-luna-SR2-linux-gtk-x86_64.tar.gz 在Linu ...

  10. 在IDEA中编写Spark的WordCount程序

    1:spark shell仅在测试和验证我们的程序时使用的较多,在生产环境中,通常会在IDE中编制程序,然后打成jar包,然后提交到集群,最常用的是创建一个Maven项目,利用Maven来管理jar包 ...

随机推荐

  1. CRISPR/Cas9|InParanoid|orthoMCL|PanOCT|pan genome|meta genome|Core gene|CVTree3|

    生命组学: 泛基因组学:用于描述一个物种基因组,据细菌基因组动力学,因为细菌的基因漂移使得各个细菌之间的基因组差异很大,(单个细菌之间的基因组差异是以基因为单位的gain&loss,而人类基因 ...

  2. Word目录生成

    之所以写这篇文章,是因为每次写报告都需要生成相应目录,但常常只记得个大概,最终还得要重新百度,十分头疼,故在此记录一下. 大概分为3个步骤 步骤1 设置标题级数 进入大纲模式 选择相应级数,这里选的是 ...

  3. struts2和springmvc性能比较2

    我们用struts2时采用的传统的配置文件的方式,并没有使用传说中的0配置.spring3 mvc可以认为已经100%零配置了(除了配置spring mvc-servlet.xml外). Spring ...

  4. 使用Google App Engine开始新的网站开发学习

    继长时间的迷茫后,我发现还是回归php网站开发更适合我,或者没有那么深刻,但至少要做点事情.不知道以后将从事什么样的工作,但现在找点事情做还是很好的.所以,为了激发我学习的热情,我在网上搜了一下免费云 ...

  5. rsync auth failed on module xxx

    rsync 报错 "auth failed on module xxx", 一般有三种情况造成: 密码文件格式错误: 服务端密码文件的格式是: user:password 每个一行 ...

  6. HBase源码系列之HFile

    本文讨论0.98版本的hbase里v2版本.其实对于HFile能有一个大体的较深入理解是在我去查看"到底是不是一条记录不能垮block"的时候突然意识到的. 首先说一个对HFile ...

  7. 记录R的一些黑魔法

    通路富集结果可视化 12345678 pathway<-read.table("PTC+_transcript_pep_supp_KEGG.txt",header=T,sep ...

  8. 联想拯救者y7000使用体验

    前言 我以前的电脑是在电商平台买的二手电脑,期间觉得软件的运行速度慢,又在网上买了一个128G的固态硬盘安装上.就从大一到大四上学期这么使用了三年半的时间.因为自己需要运行一些吃内存的软件,而我的这个 ...

  9. 50-Python2和3字符编码的区别

    目录 Python2和3字符编码的区别 python2 python3 Python2和3字符编码的区别 区别点 python2 python3 print 是一个语法结构 是一个函数,print(' ...

  10. [Python之路] object类中的特殊方法

    一.object类的源码 python版本:3.8 class object: """ The most base type """ # d ...