結對第二次—文獻摘要熱詞統計及進階需求


前言


1、時間預估

PSP

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

2、初始解題思路

剛看到基本題目時,首先想到的是用python寫,畢竟寫爬蟲什麼的,第一選擇每每是python。但後來看到題目要求語言使用c++或java,因而了對比c++與java。因爲本次的做業是要寫文詞統計+爬蟲兩個簡單的程序,考慮到文詞統計可能須要對字符串進行大量的處理,又由於java已經有封裝好的Pattern、Matcher的字符串正則匹配和處理的函數,並且以前上網查到過使用jsoup(Java 的HTML解析器)寫爬蟲也很方便,因而兩人直接敲定統一使用java編寫。對文詞統計基本設計思路是寫兩個類,一個專門用來測試,另外一個包含了基本的統計字符、行數、單詞等方法。爬蟲由於以前不曾寫過,所以基本上是邊看教程邊寫。html

3、設計實現過程(java實現)

(一)WordCount基本

代碼如何組織

一共兩個類: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;
    }

(二)WordCount進階

代碼如何組織

仍然是兩個類: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實現方式算法

  • 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();

4、改進

改進方案

  • 剛開始編寫代碼時因爲沒有好好考慮封裝,所以改需求效率很低。改進方法是儘可能讓一個函數僅實現一個比較小而具體的功能,冗餘的代碼都寫成一個函數供須要的函數調用。
  • 詞組一開始覺得詞組僅包含單詞,不須要考慮相隔的分隔符。後來知道須要詞組須要加入分隔符後,考慮將準單詞和分隔符分爲兩個數組,單詞拼接爲詞組時將分隔符也加入拼接成的字符串,最後結果是成功的。
  • 經過使用第三方庫能夠很容易的獲取頁面的HTML內容,所以只須要對爬取到的內容進行邏輯處理便可,此處沒有太多難題,可是由於爬取的論文有必定數量,並且此工程中所需的摘要內容還須要進入新頁面進行抓取,致使1000條的內容就須要1000次的頁面訪問,所以爬取的效率極爲低下(經測試大體須要4-5min),這顯然是不行的。改進:第一反應就是使用多線程技術進行優化(畢竟用的第三方庫的API進行內容抓取,此處我也不知道怎麼進行優化)
    爬蟲改進:

    • 思路:暫且先將線程數定爲100,使用這100個線程同時對DOM進行處理,根據總共的論文篇數爲每一個線程制定工做任務(例如這裏有979篇論文,那麼每一個線程大概就爬取10篇論文的內容)。這樣 100個線程同時工做,就能極大提升爬蟲的效率,減小爬取總時間。同時由於100個線程同時進行數據爬取和儲存,所以若是不進行任何處理,極有可能會出現數據丟失(實際過程當中也確實出現了數據丟失的狀況),所以須要在保存數據時進行同步或者加鎖(我選擇加鎖),從而保證存儲過程當中的穩定性。
    • 結果:成功將爬取時長從4-5min優化至20-30s。
    • :極大地提升了爬蟲的效率。
    • :爬取並保存下來的論文列表順序和網站上的不一致(我的認爲這沒什麼所謂就懶得處理了)。
  • 性能分析圖(工具使用JProfiler
    優化前:

    優化後:


5、總結

經過本次實踐,咱們意識到團隊間的交流是十分重要的,由於一項工程是須要共同協做的,而每一個人對工程需求的理解還有我的的目標都是不同的,這些都會在工做的進行中暴露出來,而這些差別有可能就會成爲團隊中的矛盾,若是不能合理地解決這些矛盾,不只沒法共同合做,甚至會引發更大的衝突(適當的爭論是能夠的),最糟糕的狀況下整個項目中止也不是不可能,所以咱們認爲有效的隊內交流是十分重要的。

困難及體會

  • 在編寫WordCount時,對需求不明確,所以不斷地修改代碼。但在這過程當中我也體會到函數封裝的重要性,若是代碼有許多冗餘,需求一變動就要作大量修改。
  • 剛使用github,有些不太習慣,相信之後體會到它的好處後會克服。
  • 在對爬蟲進行優化時,因爲先前沒有考慮到效率會如此差,所以在準備使用多線程優化時,須要大幅度修改代碼,給本身添加了許多沒必要要的工做,若是能在以前考慮到效率的問題,就能在編寫代碼時提升重用性,以便在以後的處理中更簡便。

對隊友的評價

221600424

隊友對待做業十分認真,並且善於精益求精,在代碼運行效率不夠滿意時查找方法,進行優化。

221600427

隊友編程能力很強,並且樂於爆肝,往往出現BUG都不厭其煩的調試、查錯。
相關文章
相關標籤/搜索