PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 2610 | 2655 |
? Estimate | ? 估計這個任務須要多少時間 | 2610 | 2655 |
Development | 開發 | 800 | 850 |
? Analysis | ? 需求分析 (包括學習新技術) | 300 | 350 |
? Design Spec | ? 生成設計文檔 | 60 | 40 |
? Design Review | ? 設計複審 | 30 | 40 |
? Coding Standard | ? 代碼規範 (爲目前的開發制定合適的規範) | 30 | 30 |
? Design | ? 具體設計 | 180 | 200 |
? Coding | ? 具體編碼 | 850 | 820 |
? Code Review | ? 代碼複審 | 150 | 120 |
? Test | ? 測試(自我測試,修改代碼,提交修改) | 80 | 60 |
Reporting | 報告 | 50 | 40 |
? Test Repor | ? 測試報告 | 30 | 30 |
? Size Measurement | ? 計算工做量 | 20 | 30 |
? Postmortem & Process Improvement Plan | ? 過後總結, 並提出過程改進計劃 | 30 | 45 |
合計 | 2610 | 2655 |
剛看到基本題目時,首先想到的是用python寫,畢竟寫爬蟲什麼的,第一選擇每每是python。但後來看到題目要求語言使用c++或java,因而了對比c++與java。因爲本次的做業是要寫文詞統計+爬蟲兩個簡單的程序,考慮到文詞統計可能須要對字符串進行大量的處理,又由於java已經有封裝好的Pattern、Matcher的字符串正則匹配和處理的函數,並且以前上網查到過使用jsoup(Java 的HTML解析器)寫爬蟲也很方便,因而兩人直接敲定統一使用java編寫。對文詞統計基本設計思路是寫兩個類,一個專門用來測試,另外一個包含了基本的統計字符、行數、單詞等方法。爬蟲由於以前不曾寫過,所以基本上是邊看教程邊寫。html
一共兩個類:Main類(測試類)、WordCount類(集合了全部統計函數的類),
類圖:java
由於WordCount類只負責對外界的輸入作統計功能,所以我把每一個函數寫成靜態方法,外界可直接經過類名調用。countChar、countWord、countLine函數分別統計字符數、單詞數、有效行數;isChar、isWord、isLine函數分別判斷是否爲有效字符、單詞、有效行數;toWordMap函數用來將從txt文件讀取到的文本轉化爲單詞—>詞頻(key—>value)的一個hashmap結構;outPutTop10函數將這個hashmap結構轉化爲動態數組結構,進行排序,輸出top10關鍵詞;outCount函數作綜合輸出,即調用此函數一次將字符數、單詞數、有效行數、top10單詞輸出到result.txt文件中。node
最關鍵的算法在於如何正確匹配各類須要處理的字符串,還有轉化hashmap結構和對hashmap中的單詞進行排序等。整體沒有什麼特別複雜的函數,這裏展現將輸入的文本轉爲hashmap結構存儲的流程圖:
關鍵代碼:python
public static int countChar(File file) throws IOException {//計算字符數 int charnum=0; BufferedReader reader=new BufferedReader(new FileReader(file)); int c; while((c=reader.read())!=-1) { if (isChar(c)) { charnum++; } } reader.close(); return charnum; } public static int countLine(File file) throws IOException {//計算行數 int linenum=0; BufferedReader reader=new BufferedReader(new FileReader(file)); String s=null; while((s=reader.readLine())!=null) { if (isLine(s)) { linenum++; } } reader.close(); return linenum; } public static int countWord(File file) throws IOException{//計算單詞數 int wordnum=0; BufferedReader reader=new BufferedReader(new FileReader(file)); String s=null; while((s=reader.readLine())!=null) { String[] strings=toWordList(s);//分隔 for(int i=0;i<strings.length;i++) { if (isWord(strings[i])) { wordnum++; } } } return wordnum; } public static String[] outPutTop10(File file) throws IOException {//輸出top10單詞 HashMap<String, Integer> wordMap=toWordMap(file); ArrayList<Map.Entry<String,Integer>> list = new ArrayList<Map.Entry<String,Integer>>(wordMap.entrySet()); Collections.sort(list,new Comparator<Map.Entry<String,Integer>>() { //升序排序 @Override public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) { // TODO Auto-generated method stub if (o1.getValue().compareTo(o2.getValue())==0) {//若是詞頻相同 return -1; } return o2.getValue().compareTo(o1.getValue()); } }); int num=0; String[] top10=new String[10];//保存TOP10 for(Map.Entry<String,Integer> mapping:list){ if (num==10) { break; } top10[num]="<"+mapping.getKey()+">"+":"+mapping.getValue(); num++; } return top10; }
toWordMap():c++
public static HashMap<String, Integer> toWordMap(File file) throws IOException {//把txt文件內容構建爲一個HashMap結構 HashMap<String, Integer> hashMap=new HashMap<>(); BufferedReader reader=new BufferedReader(new FileReader(file)); String s=null; int index; while((s=reader.readLine())!=null) { index=0; String[] strings=toWordList(s);//分隔爲準單詞數組 while(index<strings.length) { if (isWord(strings[index])) { String lowerstring=strings[index].toLowerCase();//單詞轉爲小寫 if (hashMap.containsKey(lowerstring)) {//判斷是否重複 hashMap.put(lowerstring, hashMap.get(lowerstring)+1);//若是重複詞頻加1 } else { hashMap.put(lowerstring, 1);//若是單詞不重複初值爲1 } } index++; } } reader.close(); return hashMap; }
仍然是兩個類:Main類(測試類)、WordCount類(集合了全部統計函數的類)
類圖:git
因爲需求的變動,如今須要爲這些統計函數增長一些參數,以及對函數的一些修改。主要修改成:因爲新增了命令行可輸入參數,所以用switch對參數輸入進行判斷,賦值給相應變量;增長了一個inputProcess函數,消除編號行以及Abstract: ,Title: 這兩個字符串,以便與以後的統計;把toWordMap函數改寫爲toPhraseMap函數——在基本需求中只考慮往hashmap存入單個單詞的狀況,如今增長了詞組詞頻統計功能,因而把以前的單詞也看做詞組,寫成一個存儲詞組的hashmap函數;剩下的就是一些正則表達式的修改,這個8談。github
進階需求中最複雜的代碼實現就是對詞組詞頻的統計,流程圖:
關鍵代碼,統計函數與基本需求相差不大,這裏只展現toPhraseMap函數:正則表達式
public static HashMap<String, Integer> toPhraseMap(File file,int w,int m) throws IOException {//把txt文件內容構建爲一個HashMap結構 HashMap<String, Integer> hashMap=new HashMap<>(); BufferedReader reader=new BufferedReader(new FileReader(file)); String s=null; int weight=1; while((s=reader.readLine())!=null) { if (Pattern.matches("[0-9]+", s)) {//跳過編號行 continue; } weight=1; if (Pattern.matches(".*Title: .*",s)&&w==1) {//遇到title行而且w=1,權值設爲10 weight=10; } String ns=inputProcess(s); String[] strings=toWordList(ns);//分隔出準單詞數組 String[] strings2=toBreakList(ns);//分隔出分隔符數組 String phrase=""; String lowerstring=null; boolean find=true;//find爲true表明找到符合規則的詞組 for(int i=0;i<strings.length-m+1;i++) { find=true; phrase=""; for(int j=i;j<i+m;j++) {//循環m次 if (isWord(strings[j])) { lowerstring=strings[j].toLowerCase();//轉小寫 phrase=phrase+lowerstring;//拼接爲詞組 if (m!=1&&j!=i+m-1) { if (strings[0].equals("")) { phrase=phrase+strings2[j];//拼接爲詞組 } else if (strings2[0].equals("")) { phrase=phrase+strings2[j+1];//拼接爲詞組 } } } else { find=false; break; } } if (find) {//若是找到一個詞組 if (hashMap.containsKey(phrase)) {//若是重複詞組則對應詞頻加weight hashMap.put(phrase, hashMap.get(phrase)+weight); } else { hashMap.put(phrase, weight);//不然初始化爲weight } } } } reader.close(); return hashMap; }
Java實現方式算法
- Java中能夠對html文本進行抓取和處理的第三方庫有許多,但從本次項目來看,只須要抓取一部分數據,並無過多複雜的操做,所以選擇簡單而且易上手的Jsoup。
- 使用第三方庫Jsoup能夠對目標網站(CVPR)的HTML內容進行,抓取,並使用jsoup.nodes.Document對內容的DOM樹進行處理,即可以獲取相應內容。
查看網頁HTML:
- 對獲取的Document使用select定位到想要獲取數據的節點,獲取數據同時儲存在一個ArrayList中。
獲取的關鍵數據:
- 待爬取結束後按照要求將數據格式化保存在文件中。
保存:
- 流程圖
關鍵代碼編程
1.多線程 //建立100個線程同時工做,並讓當前線程等待這些線程 ArrayList<Thread> threads = new ArrayList<Thread>(); for (int i = 0; i < threadnum; i++) { Thread newThread = new Thread(new absThread(i, dealnum, paperdt.size(), paperdt, pList)); newThread.start(); threads.add(newThread); } for (int i = 0; i < threads.size(); i++) { threads.get(i).join(); } 2.加鎖保護數據 lock.lock(); pList.add(title, abs); lock.unlock();
改進方案
- 剛開始編寫代碼時因爲沒有好好考慮封裝,所以改需求效率很低。改進方法是儘可能讓一個函數僅實現一個比較小而具體的功能,冗餘的代碼都寫成一個函數供須要的函數調用。
- 詞組一開始覺得詞組僅包含單詞,不須要考慮相隔的分隔符。後來知道須要詞組須要加入分隔符後,考慮將準單詞和分隔符分爲兩個數組,單詞拼接爲詞組時將分隔符也加入拼接成的字符串,最後結果是成功的。
經過使用第三方庫能夠很容易的獲取頁面的HTML內容,所以只須要對爬取到的內容進行邏輯處理便可,此處沒有太多難題,可是由於爬取的論文有必定數量,並且此工程中所需的摘要內容還須要進入新頁面進行抓取,致使1000條的內容就須要1000次的頁面訪問,所以爬取的效率極爲低下(經測試大體須要4-5min),這顯然是不行的。改進:第一反應就是使用多線程技術進行優化(畢竟用的第三方庫的API進行內容抓取,此處我也不知道怎麼進行優化)
爬蟲改進:
- 思路:暫且先將線程數定爲100,使用這100個線程同時對DOM進行處理,根據總共的論文篇數爲每一個線程制定工做任務(例如這裏有979篇論文,那麼每一個線程大概就爬取10篇論文的內容)。這樣 100個線程同時工做,就能極大提升爬蟲的效率,減小爬取總時間。同時由於100個線程同時進行數據爬取和儲存,所以若是不進行任何處理,極有可能會出現數據丟失(實際過程當中也確實出現了數據丟失的狀況),所以須要在保存數據時進行同步或者加鎖(我選擇加鎖),從而保證存儲過程當中的穩定性。
- 結果:成功將爬取時長從4-5min優化至20-30s。
- 利:極大地提升了爬蟲的效率。
- 弊:爬取並保存下來的論文列表順序和網站上的不一致(我的認爲這沒什麼所謂就懶得處理了)。
性能分析圖(工具使用JProfiler)
優化前:
優化後:
經過本次實踐,咱們意識到團隊間的交流是十分重要的,由於一項工程是須要共同協做的,而每一個人對工程需求的理解還有我的的目標都是不同的,這些都會在工做的進行中暴露出來,而這些差別有可能就會成爲團隊中的矛盾,若是不能合理地解決這些矛盾,不只沒法共同合做,甚至會引發更大的衝突(適當的爭論是能夠的),最糟糕的狀況下整個項目中止也不是不可能,所以咱們認爲有效的隊內交流是十分重要的。
221600424
隊友對待做業十分認真,並且善於精益求精,在代碼運行效率不夠滿意時查找方法,進行優化。
221600427
隊友編程能力很強,並且樂於爆肝,往往出現BUG都不厭其煩的調試、查錯。