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.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。編碼過程中也在思考設計模式可以在里面充當什么角色,所以根據命令的不同,我使用了工廠模式,程序可擴展性算中等。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM