1. 程式人生 > >軟件工程—WC功能實現 (JAVA)

軟件工程—WC功能實現 (JAVA)

要求 目錄 arsc 主類 read 準備 dsc row 源文件

軟件工程—WC功能實現(JAVA)

Github項目地址:https://github.com/Ousyoung/wc

項目要求

? wc.exe 是一個常見的工具,它能統計文本文件的字符數、單詞數和行數。這個項目要求寫一個命令行程序,模仿已有wc.exe 的功能,並加以擴充,給出某程序設計語言源文件的字符數、單詞數和行數。實現一個統計程序,它能正確統計程序文件中的字符數、單詞數、行數,以及還具備其他擴展功能,並能夠快速地處理多個文件。

基本要求

? -c [filename]統計文件字符數
-w [filename]統計文件詞數
-l [filename]統計文件行數
擴展功能
-s [filename]遞歸處理目錄下符合條件得文件
-a [filename]返回文件代碼行 / 空行 / 註釋行
? 支持各種文件的通配符(*,?)
高級功能
-x 圖形化界面(未實現

PSP

PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃 40 60
· Estimate · 估計這個任務需要多少時間 40 60
Development 開發 850 1400
· Analysis · 需求分析 (包括學習新技術) 60 120
· Design Spec · 生成設計文檔 40 120
· Design Review · 設計復審 (和同事審核設計文檔) 40 60
· Coding Standard · 代碼規範 (為目前的開發制定合適的規範) 20 60
· Design · 具體設計 80 120
· Coding · 具體編碼 500 800
· Code Review · 代碼復審 40 30
· Test · 測試(自我測試,修改代碼,提交修改) 70 90
Reporting 報告 80 90
· Test Report · 測試報告 40 50
· Size Measurement · 計算工作量 10 20
· Postmortem & Process Improvement Plan · 事後總結, 並提出過程改進計劃 30 20
合計 970 1550

解題思路

? 此次項目是模擬WC統計文件字,詞,行信息,很容易想到用JAVA中的I/O流來讀取文件信息,對於讀取文件字符數以及行數,這些都比較簡單,字符數只需讀出字符匹配不是換行符即可,行數只需沒讀到換行符就加一,基本功能當中就屬詞的統計較為不易,詞的界定相對來說要模糊的多,所以想到需要用正則表達式來匹配字符串,替換掉一些分隔符號,繼而使詞的匹配簡單化。

? 擴展功能方面,無非是在以上基礎上再多加細分和應用。代碼行/空行/註釋行同樣需要使用正則表達式來進行匹配,其界定一開始自己也很苦惱,但上網查閱資料,弄明白後也就迎刃而解了;至於文件的遞歸查找,只需要設計一個遞歸方法,遞歸查找指定目錄下所有文件名,再通過用戶鍵入的關鍵字作為方法參數,用contains(string)方法檢索出符合條件的文件即可。

設計實現

此次項目中只寫了兩個類,一個是存放各種統計方法的WC類,另一個是運行程序的主類Main類,用於啟動程序,裏面還包括了命令獲取,命令匹配的代碼。

運行程序,main方法啟動,系統顯使用說明,再通過用戶鍵入得到命令,考慮到項目要求的命令顯示,需要把命令拆分成兩部分:功能選擇function以及文件路徑path,繼而需要調用split(" ")方法,用空格分割命令得到子命令,再用多個if語句的嵌套,匹配調用WC類中不同的靜態方法,將path作為參數輸入,找到指定文件。

設計主類Main類中的靜態方法:詞統計wordsCount,代碼行,空行統計complexCount,文件遞歸處理fileHandle 等(只列舉了部分,詳見代碼說明

類,方法調用關系圖如下

技術分享圖片

代碼說明

main方法

數據的輸入輸出處理,命令的提取,匹配都在這一部分,下面主要展示命令的提取,匹配部分。這裏使用nextLIne方法獲取命令,再用split分割命令,得到子命令,再進行匹配;外層使用whlie循環訪問,直至用戶鍵入退出命令 -esc 退出

while (true) {
            Scanner getIn = new Scanner(System.in);
            if (getIn.hasNext())
                command = getIn.nextLine(); // 獲取指令
            if (command.equals("-esc")) {
                System.out.println("***WC.exe程序已關閉");
                System.exit(0); // 關閉程序
            } else {
                String[] commands = new String[2]; // 字符串數組用於存放指令
                commands = command.split(" ", 2); // 拆分用戶輸入的指令
                function = commands[0]; // command前半部分為功能選擇
                path = commands[1]; // 後半部分為文件路徑
                // Mark:修改代碼時出現了數組下標越界的問題*****************
                if (function.equals("-c"))
                    WC.charsCount(path);
                else if (function.equals("-w"))
                    WC.wordsCount(path);
                else if (function.equals("-l"))
                    WC.rowsCount(path);
                else if (function.equals("-a"))
                    WC.complexCount(path);
                else if (function.equals("-all"))
                    WC.allCount(path);
                else if (function.equals("-s")) {
                    System.out.print("請輸入文件叠代查找的關鍵字:\n");
                    Scanner getIn2 = new Scanner(System.in);
                    // 需要拿到指定字符串,這裏的方法需要兩個參數
                    WC.fileHandle(path, getIn2.nextLine());
                } else
                    System.out.println("用戶輸入不合法,請重新輸入!");
            }
        }

WC類

統計字符 charsCount

比較簡單,所以只展示關鍵代碼

try {
            int tempchar;
            readFile = new InputStreamReader(new FileInputStream(file));
            while ((tempchar = readFile.read()) != -1) {
                if ((char) tempchar != ‘\r‘ && (char) tempchar != ‘\n‘) {
                    // 只要匹配的不是換行符,字符數就加一
                    charsNum++;
                }
            }
            readFile.close();
            System.out.println("文件路徑:" + fileName + "  文件的字符數為: " + charsNum);
        }

統計行 rowsCount

同樣比較簡單,所以只展示關鍵代碼

try {
            readFile = new InputStreamReader(new FileInputStream(file));
            int tempchar;
            while ((tempchar = readFile.read()) != -1) {
                if ((char) tempchar == ‘\n‘) {
                    // 匹配到換行符,行數就加一
                    lineNum++;
                }
            }
            System.out.println("文件路徑:" + fileName + "  文件的行數為: " + lineNum);
            readFile.close();
        }

統計詞 wordsCount

使用正則表達式,匹配字符,將文件字符串中的 . * " " / 等符號用reaplaceAll方法替換成空格,再用split方法用空格分割字符串,得到詞

static void wordsCount(String fileName) throws IOException {
        // 返回該文件的詞的數目
        File file = new File(fileName);
        BufferedReader bur = null;
        String string = "";
        String line;
        int wordNum = 0;
        String words[] = null;
        try {
            bur = new BufferedReader(new FileReader(file));
            while ((line = bur.readLine()) != null) {
                String s = line.replaceAll("[\\p{Punct}\\s\\p{Nd}\\uffe5\\u4e00-]", " ");   
                // 得到除字符外全是空格的文本
                string = string + s + " ";
                // 這裏要加空格,否則string每次增長,行末的詞會和下一行首詞相連
            }
            words = string.split(" ");
            // 去除空格,得到詞的字符串數組
            wordNum = words.length;
            System.out.println("文件路徑:" + fileName + "  文件的詞數為: " + wordNum);
            bur.close();
            } catch (FileNotFoundException e) {
        System.out.println("***系統提示:找不到指定的文件!請重新輸入:");
    }
}

統計代碼行,註釋行等 complexCount

使用正則表達式匹配相應行:“((//)|(/+)|((^\s))|((^\s)+/))+” 註釋行,"^\s$"空白行,"(?!import|package).+;\s(((//)|(/+)).)" 代碼行

static void complexCount(String fileName) throws IOException {
        // 返回該文件的代碼行 / 空行/註釋行
    File file = new File(fileName);
    BufferedReader bufr = null;
    String line = null;
    int codeLine = 0;
    int blankLine = 0;
    int noteLine = 0;
    Pattern codePattern = Pattern.compile("(?!import|package).+;\\s*(((//)|(/\\*+)).*)*",
            Pattern.MULTILINE + Pattern.DOTALL);
    // 匹配代碼行 // Mark:匹配代碼曾行時出現了錯誤,還有優化空間*********
    Pattern blankPattern = Pattern.compile("^\\s*$");
    // 匹配空白行
    Pattern notePattern = Pattern.compile("((//)|(/\\*+)|((^\\s)*\\*)|((^\\s)*\\*+/))+",
            Pattern.MULTILINE + Pattern.DOTALL);
    // 匹配註釋行
    try {
        bufr = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
        while ((line = bufr.readLine()) != null) {

            if (codePattern.matcher(line).matches())
                codeLine++;
            if (blankPattern.matcher(line).find())
                blankLine++;
            if (notePattern.matcher(line).find())
                noteLine++;
        }
        System.out.println(
                "文件路徑:" + fileName + "  文件的代碼行 / 空行 / 註釋行數為: " + codeLine + "/" + blankLine + "/" + noteLine);
        bufr.close();
    } catch (FileNotFoundException e) {
        System.out.println("***系統提示:找不到指定的文件!請重新輸入:");
    }
  }
}

文件遞歸處理 fileHandle 及 allCount

遞歸返回目錄下所有文件名,再調用String類的contains()方法模糊匹配所有文件名含指定字符串str的文件,再調用allCount方法返回各個文件的詞,字符,行信息

static void fileHandle(String fileName, String str) throws IOException {
        // 文件叠代處理
        List<File> fileList = new ArrayList<File>();
        File file = new File(fileName);
        File[] files = file.listFiles();
        // 獲取目錄下的所有文件或文件夾
        if (files == null) {
            // 如果目錄為空,直接退出
            System.out.println("***系統提示:找不到指定路徑!請重新輸入:");
        }
        // 遍歷,目錄下的所有文件
        for (File f : files) {
            if (f.isFile()) {
                fileList.add(f);
            } else if (f.isDirectory()) {
                System.out.println(f.getAbsolutePath());
                fileHandle(f.getAbsolutePath(), str);
            }
        }
        for (File f1 : fileList) {
            if (f1.getName().contains(str) == true) {
                // 文件名包含指定字符串,contains()方法值為true
                WC.allCount(fileName + "/" + f1.getName());
                // f1.getName()只是文件名,這裏要在f1.getName()前加入作為參數傳入的fileName路徑,否則會找不到文件
            }
        }
        // System.out.println("測試代碼"); 左側為測試用代碼
    }
static void allCount(String fileName) throws IOException {
    // 返回詳細信息
    WC.charsCount(fileName);
    WC.wordsCount(fileName);
    WC.rowsCount(fileName);
    WC.complexCount(fileName);
    System.out.println("\n");
}

運行測試

-c -w -l -a 測試 readme.txt文件

由於我寫了一個allCount方法(命令 -all ),相當於一次執行-w -c -l -a ,所以對於以上命令只做部分測試

技術分享圖片

-all 測試 readme.txt文件

技術分享圖片

-s 測試 檢索test目錄下文件名含one的文件(onechar, oneword ,oneline,one)

技術分享圖片

遞歸處理 .java源代碼測試

技術分享圖片

註:(上面截圖第二行文字中“叠代查找”應為“遞歸查找”,截圖的時候沒註意,寫錯了......代碼中已更改)

代碼覆蓋率

技術分享圖片

項目小結

? 此次項目是軟件工程的第一次項目,總的來說感覺自己做的有些倉促,許多細節的地方還有優化的空間,比如正則表達式匹配詞那裏,對於一些中文詞沒有考慮在內;還有main方法裏完全可以寫一個匹配方法來處理用戶命令,是代碼結構看起來更加合理;部分代碼(I/O流的創建)有些繁瑣,可以再簡潔化一點,達到更低的時間復雜度。不過,這次項目的收獲也不少,首先是對正則表達式的掌握有了提升,以前自己都沒怎麽仔細了解正則表達式,做項目的時候,邊做邊學,現在自己也能寫一些正則表達式了。再者,通過軟件工程理論的學習,開始了解編程的設計步驟,從無到有,一步一步來,效率有所提高,正如老師所說的,前面的準備工作做足,考慮更全面,後面的代碼編寫就會輕松高效很多。

軟件工程—WC功能實現 (JAVA)