要求0:作業要求地址【https://edu.cnblogs.com/campus/nenu/2016CS/homework/2110】
要求1:git倉庫地址【https://git.coding.net/Jingr98/wf.git】
要求2:
1.PSP階段表格
SP2.1 |
任務內容 |
計划共完成需要的時間(min) |
實際完成需要的時間(min) |
Planning |
計划 |
40 |
50 |
Estimate |
估計這個任務需要多少時間,並規划大致工作步驟 |
40 |
50 |
Development |
開發 |
810 |
1030 |
Analysis |
需求分析 (包括學習新技術) |
90 |
130 |
Design Spec |
生成設計文檔 |
40 |
40 |
Design Review |
設計復審 (和同事審核設計文檔) |
0 |
0 |
Coding Standard |
代碼規范 (為目前的開發制定合適的規范) |
0 |
40 |
Design |
具體設計 |
90 |
90 |
Coding |
具體編碼 |
480 |
600 |
Code Review |
代碼復審 |
40 |
50 |
Test |
測試(自我測試,修改代碼,提交修改) |
70 |
80 |
Reporting |
報告 |
180 |
210 |
Test Report |
測試報告 |
100 |
120 |
Size Measurement |
計算工作量 |
40 |
40 |
Postmortem & Process Improvement Plan |
事后總結, 提出過程改進計划 |
40 |
50 |
功能模塊 |
具體階段 |
預計時間(min) |
實際時間(min) |
功能1 |
具體設計 具體編碼 測試完善 |
20 130 20 |
30 200 25 |
功能2 |
具體設計 具體編碼 測試完善 |
30 140 20 |
15 150 25 |
功能3 |
具體設計 具體編碼 測試完善 |
40 210 30
|
45 250 30 |
2.分析預估耗時和實際耗時的差距原因:
(1)在分析預估耗時時,沒有過多考慮編程中的細節問題。編程過程中不斷有新的問題出現。
(2)對Java語言掌握不夠熟練,編程中學習的時間較長
(3)最主要的原因就是一開始審題不清,沒有考慮到輸出樣例格式的問題。導致項目將要完工時又要整改很多地方,浪費了好多時間。
要求3:
1.解題思路描述
(1)看到題目后,我想了一下這個程序大致有三個步驟:讀取文本、統計單詞、(排序)輸出。有了這個框架后,我從最簡單的功能1嘗試編寫,在獲取到文本內容需要對字符串進行分割時我遇到了一些問題(因為不是很熟悉正則表達式),所以查閱了相關教程,自己嘗試寫了一下可以達到預期效果,但是需要兩次正則表達式的運用(一是對字符串按空格和非字母數字符號進行分割得到字符串數組,二是對得到的字符串數組通過字母開頭規則過濾掉那些非法單詞),自我感覺這里編寫的不是太好。實現了功能1后我開始看功能2,發現只要得到文件夾下的文件名數組,排序后返回指定文件路徑后就可以參照功能1的實現。功能3的話只要在前者的基礎上傳入參數-n,對list進行排序后根據-n輸出結果。
(2)最初編寫時我是把功能1和功能2寫在了一起,即用一個count()函數實現兩個功能。功能1直接調用count()就可以實現詞頻統計,功能2則需要先調用readDir()和setpath()方法得到指定文件路徑,然后再調用count()就可以了。功能3則是用count( int n )實現。但是后來我仔細看了題目后發現,兩者的輸出樣例是不一樣的!發現了這個問題后,我本來想寫兩個輸出結果的方法分別對應上述兩種情況,但是嘗試了一下報了很多錯誤,就不敢大改了,只能選擇把兩種情況完全分開處理,於是就有了現在的 countFile()和countDir()分別對應功能1和功能2,countNum()對應功能3。這樣雖然解決了樣例輸出的問題,但是代碼重復量真的很大。
2.代碼介紹
(1)困難點:功能1主要是對字符串的處理(正則表達式的運用),如何得到合法單詞;功能2主要是獲取某文件夾下的所有文件名,並按照文件名排序后返回指定文件,其余就參照功能1的實現;功能3主要是對詞頻進行排序,並按照參數進行輸出。其實我感覺這三個功能分開來寫不是很難,對我來說,最困難的就是如何減少代碼的重復。三個功能明顯有重復的部分,應該把哪些部分拎出來寫成公共的方法,是我應該繼續思考的!
(2)代碼片段
簡單介紹一下我的代碼,總體上用Java寫了兩個類:wf類(包含各種功能方法)和wfTest類(分情況對wf類里的方法進行調用,用來測試)。
1) isLegal()函數:判斷是否為合法單詞
1 public boolean isLegal(String word) { 2 String regex="^[a-zA-Z][a-zA-Z0-9]*$"; 3 Pattern p = Pattern.compile(regex); 4 Matcher m =p.matcher(word); 5 if(m.matches()) { 6 return true; 7 }else { 8 return false; 9 } 10 }
2) readDir()函數:獲取文件夾下所有文件的文件名,對數組進行排序並返回第一個元素
1 public String readDir(String filepath){ 2 File file = new File(filepath); 3 String[] filelist = file.list(); 4 String[] namelist = new String [filelist.length]; 5 for(int i=0;i<filelist.length;i++) { 6 File readfile = new File(filepath+"\\"+filelist[i]); 7 namelist[i]=readfile.getName(); 8 } 9 List<String> list = (List<String>)Arrays.asList(namelist); 10 Collections.sort(list); 11 String[] paths = list.toArray(new String[0]); 12
13 return paths[0]; 14 }
3) countFile()函數:當輸入格式為【wf -c 文件名 】時調用,並輸出結果
1 public void countFile() { 2 try { 3 FileInputStream inputStream = new FileInputStream(new File(path)); 4 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); 5 //將文件內容存入words中
6 while((lineword=bufferedReader.readLine())!=null) { 7 words+=lineword+"\n"; 8 } 9 //全部轉換成小寫,達到不區分大小寫的目的
10 String str=words.toString().toLowerCase(); 11 //分割字符串並存入數組
12 String[] word = str.split("[^a-zA-Z0-9]|\\ "); 13 int num =0; 14 Map<String,Integer> myMap = new TreeMap<String,Integer>(); 15 //遍歷數組將其存入Map<String,Integer>中
16 for(int i=0;i<word.length;i++) { 17 //首先判斷是否為合法單詞,合法則存入map中
18 if(isLegal(word[i])) { 19 if(myMap.containsKey(word[i])) { 20 num = myMap.get(word[i]); 21 myMap.put(word[i], num+1); 22 } 23 else { 24 myMap.put(word[i], 1); 25 } 26 } 27 } 28 //將map.entrySet()轉換成list
29 List<Map.Entry<String, Integer>> list =new ArrayList<Map.Entry<String,Integer>>(myMap.entrySet()); 30 //輸出結果
31 System.out.println("total"+" "+list.size()+"\n"); 32 // for(int i=0;i<list.size();i++) { 33 // Map.Entry<String, Integer> e =list.get(i); 34 // System.out.println(e.getKey()+" "+e.getValue()); 35 // }
36 for(int i=0;i<word.length;i++) { 37 if(myMap.containsKey(word[i])) { 38 System.out.printf("%-14s%d\n",word[i],myMap.get(word[i]));
39 myMap.remove(word[i]); 40 } 41 } 42 bufferedReader.close(); 43 }catch(FileNotFoundException e) { 44 e.printStackTrace(); 45 }catch(IOException e) { 46 e.printStackTrace(); 47 } 48 }
4) countDir()函數:當輸入格式為【wf -f 文件路徑 】時調用,並輸出結果。(和countFile函數基本一致,只是輸出格式上有些不同)

1 public void countDir() { 2 try { 3 FileInputStream inputStream = new FileInputStream(new File(path)); 4 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); 5 //將文件內容存入words中
6 while((lineword=bufferedReader.readLine())!=null) { 7 words+=lineword; 8 } 9 //全部轉換成小寫,達到不區分大小寫的目的
10 String str=words.toString().toLowerCase(); 11 //分割字符串並存入數組
12 String[] word = str.split("[^a-zA-Z0-9]|\\ "); 13 int num =0; 14 Map<String,Integer> myMap = new TreeMap<String,Integer>(); 15 //遍歷數組將其存入Map<String,Integer>中
16 for(int i=0;i<word.length;i++) { 17 //首先判斷是否為合法單詞
18 if(isLegal(word[i])) { 19 if(myMap.containsKey(word[i])) { 20 num = myMap.get(word[i]); 21 myMap.put(word[i], num+1); 22 } 23 else { 24 myMap.put(word[i], 1); 25 } 26 } 27 } 28 //將map.entrySet()轉換成list
29 List<Map.Entry<String, Integer>> list =new ArrayList<Map.Entry<String,Integer>>(myMap.entrySet()); 30 //輸出結果
31 System.out.println("total"+" "+list.size()+" words"); 32 for(int i=0;i<list.size();i++) { 33 Map.Entry<String, Integer> e =list.get(i); 34 System.out.println(e.getKey()+" "+e.getValue()); 35 } 36 bufferedReader.close(); 37 }catch(FileNotFoundException e) { 38 e.printStackTrace(); 39 }catch(IOException e) { 40 e.printStackTrace(); 41 } 42 }
5) countNum()函數:當輸入格式為【wf -f 文件路徑 -n 數量】或者【wf -c 文件名 -n 數量】或者【wf -n 數量 -c 文件名】或者【wf -n 數量 -f 文件路徑】時調用,並輸出結果
1 public void countNum(int n) { 2 try { 3 FileInputStream inputStream = new FileInputStream(new File(path)); 4 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); 5 //將文件內容存入words中
6 while((lineword=bufferedReader.readLine())!=null) { 7 words+=lineword+"\n"; 8 } 9 //全部轉換成小寫,達到不區分大小寫的目的
10 String str=words.toString().toLowerCase(); 11 //分割字符串並存入數組
12 String[] word = str.split("[^a-zA-Z0-9]|\\ "); 13 int num =0; 14 Map<String,Integer> myMap = new TreeMap<String,Integer>(); 15 //遍歷數組將其存入Map<String,Integer>中
16 for(int i=0;i<word.length;i++) { 17 //首先判斷是否為合法單詞
18 if(isLegal(word[i])) { 19 if(myMap.containsKey(word[i])) { 20 num = myMap.get(word[i]); 21 myMap.put(word[i], num+1); 22 } 23 else { 24 myMap.put(word[i], 1); 25 } 26 } 27 } 28 //將map.entrySet()轉換成list
29 List<Map.Entry<String, Integer>> list =new ArrayList<Map.Entry<String,Integer>>(myMap.entrySet()); 30 //通過比較器實現排序
31 Collections.sort(list,new Comparator<Map.Entry<String, Integer>>(){ 32 public int compare(Entry<String,Integer> e1,Entry<String,Integer> e2) { 33 return e2.getValue().compareTo(e1.getValue()); 34 } 35 }); 36 //輸出結果
37 System.out.println("Total words is "+list.size()); 38 System.out.println("----------"); 39 for(int i=0;i<n;i++) { 40 Map.Entry<String, Integer> e =list.get(i); 41 System.out.printf("%-14s%d\n",e.getKey(),e.getValue());
42 } 43 bufferedReader.close(); 44 }catch(FileNotFoundException e) { 45 e.printStackTrace(); 46 }catch(IOException e) { 47 e.printStackTrace(); 48 } 49 }
6)setpath()函數:創建對象時若沒有傳入路徑,則給變量path賦值
public void setpath(String path) { this.path=path; }
7)最后,在wfTest類里,我通過 if else 語句對控制台輸入的字符串分情況討論,調用相應的方法
1 package wf; 2 import java.util.*; 3
4 public class wfTest{ 5 public static void main(String[] args) { 6 Scanner input = new Scanner(System.in); 7 String str = ""; 8 str = input.nextLine(); 9 String[] splt = str.split(" "); 10 String path; 11 int num=splt.length; 12 if(num==3) { 13
14 if(splt[1].equals("-c")) { 15 path=splt[2]; 16 wf test = new wf(path); 17 test.countFile(); 18 }else if(splt[1].equals("-f")){ 19
20 wf test = new wf(); 21 path=test.readDir(splt[2]); 22 // System.out.println(splt[2]);
23 test.setpath(splt[2]+"\\"+path); 24 test.countDir(); 25 }else { 26 System.out.println("輸入格式有錯誤"); 27 } 28
29 }else if(num==5) { 30 if(splt[1].equals("-f")&&splt[3].equals("-n")) { 31 wf test = new wf(); 32 path=test.readDir(splt[2]); 33 test.setpath(splt[2]+"\\"+path); 34 test.countNum(Integer.parseInt(splt[4])); 35 }else if(splt[1].equals("-c")&&splt[3].equals("-n")) { 36 path=splt[2]; 37 wf test = new wf(path); 38 test.countNum(Integer.parseInt(splt[4])); 39 }else if(splt[1].equals("-n")&&splt[3].equals("-c")) { 40 path=splt[4]; 41 wf test = new wf(path); 42 test.countNum(Integer.parseInt(splt[2])); 43 }else if(splt[1].equals("-n")&&splt[3].equals("-f")) { 44 wf test = new wf(); 45 path=test.readDir(splt[4]); 46 test.setpath(splt[4]+"\\"+path); 47 test.countNum(Integer.parseInt(splt[2])); 48 }else { 49 System.out.println("輸入格式有錯誤"); 50 } 51 }else { 52 System.out.println("輸入格式有錯誤"); 53 } 54
55 input.close(); 56 } 57 }
8)運行結果展示:
此截圖是在eclipse平台上運行的效果,但是項目里已經生成 wf.exe 執行文件,可以在控制台進行測試(這是我第一次用jar包通過exe4j生成可執行文件,因為在exe4j上忘記選擇生成控制台程序(默認是gui程序),所以在這里糾結了好久,真的是。。。為了避免大家犯同樣的錯誤,在此誠摯地推薦一篇相關博客:https://blog.csdn.net/u011752272/article/details/80697198)
作業輸出樣例:
測試輸出樣例:
3.個人感想
通過此次項目的經歷,我覺得代碼的規范性很重要。這次作業我相當於寫了兩個版本,最終的代碼是在初期代碼的基礎上改了很多,這個改代碼的時間真的快趕上我寫出程序的時間了。一開始自己沒想着如何整體對項目結構進行設計,就一步步按照自己的想法寫完了,但是回過頭去改的時候,發現結構有些亂,改的時候需要兼顧很多東西,真的不太好。這讓我想到了《構建之法》第四章所講的有關代碼規范的重要性,現在看自己編寫的代碼都別扭與復雜,更別說在合作的過程中他人看自己的代碼的感受了。希望自己可以通過此次作業,在這方面有所改進。