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

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

Github代碼簽入記錄


PSP表格

PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃
- Estimate 估計這個任務須要多少時間 1275 2145
Development 開發
- Analysis 需求分析 (包括學習新技術) 180 210
- Design Spec 生成設計文檔 100 150
- Design Review 設計複審 60 40
- Coding Standard 代碼規範 (爲目前的開發制定合適的規範) 30 20
- Design 具體設計 30 40
- Coding 具體編碼 600 520
- Code Review 代碼複審 60 70
- Test 測試(自我測試,修改代碼,提交修改) 120 980
Reporting 報告
- Test Report 測試報告 45 60
- Size Measurement 計算工做量 20 25
- Postmortem & Process Improvement Plan 過後總結, 並提出過程改進計劃 30 30
合計 1275 2145

解題思路

需求分析

首先,咱們組內對任務需求進行分析,結合以前的代碼經驗,將問題劃分爲兩大步驟。第一是對文件進行讀取,得到文件內容。第二是對讀取到文件內容進行分析及統計。因而建立出Main、Process、Result三個類,做用分別Main是程序入口、Process對讀取到的文件內容進行分析統計、Result作爲結構記錄相應字符數、行數和單詞及其頻數。而後在Process類中統計字符數用String類的Length()算出字符數並記錄,統計行數時經過按行讀取文件計算出行數並記錄,統計單詞時依靠正則表達式匹配單詞、統計頻次並記錄到Map結構中。並做出相應的類圖使需求在類圖都有對應的方法獲得實現
最後設計比較刁鑽的測試數據測試代碼正確性、而且在代碼中用時相對較長的部分進行力所能及的代碼優化。html

設計實現過程

類圖展現

算法關鍵及流程圖

-字符、行數、單詞統計

程序性能改進及思路

基礎及進階需求均使用工具JProfiler監控處理隨機生成的約700m大小文件分析內存,處理器佔用。java

通用優化部分

在這次程序編寫的過程當中,主要處理程序迭代了兩次.在第一個原型程序時由於採用了按行讀取再按字處理的方式致使效率較爲低下,一個字符一般要用幾個IF語句判斷是否符合需求的要求,再加上不利於後續功能擴展便將其從新編寫,採用正則表達式按行處理文件.ios

不論在基礎仍是進階功能的編寫中發現程序運行時要反覆調用幾個方法,而在方法中要不斷建立數個Pattern類來構建正則表達式,所以在優化的過程當中首先考慮將方法設置爲靜態類型,並將幾個Pattern類的正則表達式在Process類構造的時候便進行編譯,提升效率.
測試效果採用一個21M大小左右文件進行
基礎未優化前
git

基礎優化後:
github

進階需求採用一個約22M大小文件測試
優化前:
正則表達式

優化後
算法

有必定效果但並非十分明顯api

基礎需求部分改進思路

各方法耗時:數組

能夠發如今哈希表處理的時候耗時佔比較多.在程序中爲了節約時間咱們便直接使用JAVA提供的map結構儲存單詞,數量的鍵值對,可是JAVA自己提供的方法不能很好的知足咱們的需求好比說單詞數量排序的問題,目前解決方案是將MAP導出爲一個list表,對此進行排序.在咱們規劃中是採用一個相似於treemap的順序結構來進行有序插入,在節約排序時間的同時節約大量爲了排序重複儲存的內存空間.可是自定義結構須要花費大量時間測試可靠性,而且完成此類型超大文件所需的時間在一分鐘左右,不算太過離譜,因而便計劃在完成全部任務後最後進行.服務器

此外因爲前期分析得當,提早採用了Bufferedreader以帶緩衝的方式讀取文件,節約了大量的IO時間,使得即使在機械硬盤上文件讀取部分花費時間佔比不到10%,沒有進一步優化的必要.

進階需求部分改進思路

各方法耗時:

從圖中能夠得出在使用正則表達式完成需求的耗時佔比較多,但考慮到需求並未明確說起只處理特定格式的文件,不能使用取巧辦法忽略需求中說起的無關信息,此外考慮到主要處理文件需求爲CVPR論文信息,在此類文件一般不大的狀況下考慮到緊迫的時間安排再也不進一步優化.

爬蟲部分

爬蟲部分程序主要由兩部分組成,首先爬取位於 http://openaccess.thecvf.com/CVPR2018.py 的論文連接列表,將其中的論文URL添加到帶爬取URL表中,再從URL列表取出URL不斷爬取論文信息

最開始的原型實現使用單線程進行,因爲該網站服務器位於境外,再加上帶爬取URL有千條之多,所以速度十分緩慢.
採用JProfiler分析時間佔用,通過團隊團隊討論後發現,第二部分爲大量獨立任務構成,十分適合進行多線程優化,而且由於各個結果之間相互獨立,不須要太多的多線程同步處理,還利於程序編寫.所以咱們首先對此進行了優化.

優化前:

大約花費293秒

優化後:

大約花費25秒,效率提高十分明顯
在此基礎上,咱們進一步分析發現由於受制於該網站服務器位於境外的網絡緣由,每一個線程進行網絡通訊的時候等待時間較久且容易出錯,所以進一步的優化考慮爲採用位於境外代理池替換本機進行信息爬取,但受制於成本以及時間並未付諸實施.

關鍵代碼展現

基礎部分封裝的3個API函數

public int getWord_count() { return result.getWord_count(); }

    public int getChar_count() { return result.getChar_count(); }

    public int getLine_count() { return result.getLine_count(); }

基礎部分統計單詞函數

private static void process_line_withRegularExpression(String str, Result resultClass){
        Matcher m=p.matcher(str);
        while(m.find()) {
            //System.out.println(m.group(2));
            resultClass.addWord(m.group(2));
        }
    }

基礎部分統計字符數代碼

while ((s = bufferedReader.readLine()) != null) {
                i++;//行計數
                resultClass.char_count_plus(s.length());//統計字符數
                s = s.replaceAll("[^\\x00-\\x80]", "");
                s=trim(s);//去掉開頭不顯示字符
                if(s.length()!=0){//如有可顯示字符則處理
                    resultClass.line_count_plus();//統計行數
                    process_line_withRegularExpression(s,resultClass);
                }
            }

基礎部分單詞數量統計彙總函數

public void addWord(String word) {
        String tempWord = word.toLowerCase();
        if (resultMap.containsKey(tempWord)) {//resultMap使用HashMap結構
            resultMap.put(tempWord, resultMap.get(tempWord) +  1);
            word_count_plus();
        } else {
            resultMap.put(tempWord, 1);
            word_count_plus();
        }
    }

通用單詞數量排序函數

public List<Map.Entry<String, Integer>> sort() {
        //從HashMap恢復entry集合
        //從resultMap.entrySet()建立LinkedList。咱們將排序這個鏈表來解決順序問題。
        //咱們之因此要使用鏈表來實現這個目的,是由於在鏈表中插入元素比數組列表更快。
        List<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>(resultMap.entrySet());

        Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
        //經過傳遞鏈表和自定義比較器來使用Collections.sort()方法排序鏈表。
            //降序排序
            public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
                if(o2.getValue()==o1.getValue()) return o1.getKey().compareTo(o2.getKey());
                return o2.getValue().compareTo(o1.getValue());
                //使用自定義比較器,基於entry的值(Entry.getValue()),來排序鏈表。
            }
        });

進階部分短語基礎處理函數

private static void process_line_withPhrase(String str, Result resultClass, int number,int weight) {
        Matcher m = phrasePattern.matcher(str);
        PhraseFactory phraseFactory = new PhraseFactory(number);//短語構造類
        PhraseBorder phraseBorder;//result
        while (m.find()) {
            if (m.start() == 0 || !Character.isDigit(str.charAt(m.start() - 1))){
            //判斷單詞開頭是否爲數字
                resultClass.word_count_plus();
                if ((phraseBorder = phraseFactory.storeBorder(m.start(), m.end())) != null) {
                //儲存單詞位置
                    int newEnd = trimTail(str, phraseBorder.start, phraseBorder.end);
                    //System.out.println(str.substring(phraseBorder.start, newEnd));
                    resultClass.addPhrase(str.substring(phraseBorder.start, newEnd), weight);
                    //添加短語至結果集
                }
            }
        }
    }

短語構造函數

public PhraseBorder storeBorder(int start, int end) {//添加新的單詞位置
        if (!isFull()) {
            if (isFormerEnd(start)) {
                pool[iterator].start = start;
                pool[iterator].end = end;
                addIterator();
            } else {
                clear();
                pool[iterator].start = start;
                pool[iterator].end = end;
                addIterator();
            }
        } else {
            if (isFormerEnd(start)) {
                pool[iterator].start = start;
                pool[iterator].end = end;
                addIterator();
                int outputStart = pool[startPoint].start;
                addStartPoint();//後移開頭指針等於輸出
                return new PhraseBorder(outputStart, end);
            } else {
                clear();
                pool[iterator].start = start;
                pool[iterator].end = end;
                addIterator();
            }
        }
        return null;
    }

爬蟲代碼

public int initialization(String URL) {//得到待爬取URL列表
        try {
            Document document = Jsoup.connect(URL).timeout(1000 * 60).maxBodySize(0).get();//
            Elements links = document.getElementsByTag("a");
            int i = 0;
            for (Element link : links) {
                String href = link.attr("href");
                if (href.contains("content_cvpr_2018/html/")) {
                    URLs[i] = "http://openaccess.thecvf.com/" + href;
                    System.out.format("%d:%s\n", i, URLs[i]);
                    ++i;
                }
            }
            return URL_size = i;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return -1;
    }
    public void workMethod() {//爬取管理方法
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < URL_size; i++) {
            cachedThreadPool.execute(() -> getContent(getURL()));
        }
        try {
            bufferedWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
        cachedThreadPool.shutdown();
    }

    public void getContent(String URL) {//得到內容方法
        try {
            Document document = Jsoup.connect(URL).timeout(1000 * 600).maxBodySize(0).get();//
            Elements links = document.getElementsByTag("div");
            String content = null, title = null;
            for (Element link : links) {
                switch (link.id()) {//case "authors"://and time
                    case "papertitle":
                        title = link.text();
                        break;
                    case "abstract":
                        content = link.text();
                        break;
                }
            }
            synchronized (this) {
                System.out.println(outputNumber);
                System.out.println();
                bufferedWriter.write(String.valueOf(outputNumber));
                bufferedWriter.newLine();
                System.out.format("Title: %s\n", title);
                bufferedWriter.write(String.format("Title: %s\n", title));
                System.out.format("Abstract: %s\n", content);
                bufferedWriter.write(String.format("Abstract: %s\n", content));
                bufferedWriter.newLine();
                bufferedWriter.newLine();
                outputNumber++;
            }
            System.out.println();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public synchronized String getURL() {//得到待爬取URL
        if (currentURL < URL_size) return URLs[currentURL++];
        else return null;
    }

爬蟲結果展現:

單元測試

基礎測試部分使用C++語言編寫了一個隨機生成字符的程序,隨機生成行數、字符串長度,而後寫入到input.txt文件中。在生成字符串的同時,記錄生成字符數,行數以及定義上的空行數。而後將生成的input.txt文件作爲WordCount程序的測試文件,而後在Eclipse中使用Junit進行單元測試。

#include<cstdio>
#include<time.h>
#include<stdlib.h>
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
    srand(time(0));                         //產生隨機化種子
    int count = 0;
    int count1 = 0;
    int n = rand() % 1000+99;  
                       //在1000-99的範圍內隨機產生字符串個數 
    FILE *fp = fopen("input.txt","w");
    int temp=n;
    while (temp--)                              //依次產生n個字符串 
    {
        int k = rand() % 150 + 0;   //隨機生成一個字符串的長度 
        if(k==0){
            count1++;
        }
        count+=k;
        for (int i = 0; i < k; i++)
        {
            int x;                   
                x = rand() % (127 - 32) + 32;       
                fprintf(fp, "%c", x);                 //將x轉換爲字符輸出 
        }
        fprintf(fp, "\n");
        

    }
    printf("line: %d", n ); 
        printf("空白line: %d", count1 ); 
    printf("\n character: %d\n",count);
    return 0;
}

Junit單元測試截圖

進階需求採用官網論文文件測試

注:官網爬取結果存在非ASCII字符且位於兩個合法單詞之間的狀況, 本次程序對於此類狀況按兩個單詞處理
若按一個單詞計算此類狀況單詞數量應少掉10個左右

結果

測試數據構造思路

-統計行數
構造思路:1.中間增長空白行
2.單行放入定義空白符

-統計單詞
構造思路:1.多種不合法單詞,如1aaaa、the、abc123a等
2.分割符分割單詞,如task-masn、mesk--a123等

-統計詞組
構造思路:1.單詞間添加非法單詞,如aaaa in bbbb等
2.單詞間添加分割符,如aaaa(%bbbb等

-極端狀況處理
構造思路:生成大型文本文件(738m)測試程序穩定性

部分測試數據概覽:

character: 37854 line: 500 無效空白line: 3

character: 75998 line: 1016 無效空白line: 6

遇到的困難及隊友互評

遇到的困難及解決

在此次結對任務中遇到的困難是比較多的,首先在閱讀任務、分析需求的時候就出現了至關多的分歧,分隔符劃分、詞組匹配等。相信這也不僅是咱們趕上的困難,從微信羣裏就能夠看出,這一點能夠說是全班同窗的共同問題。這一部分的解決方法就是討論,不止結對兩人間討論,羣內討論、與其餘小組進行討論而且交換測試數據互相糾錯。其次是爬蟲部分,由於以前沒有接觸過,因此是現學的技巧。由於時間限制,因此這一部分仍是有點難度的。最後是代碼Debug,由於在需求分析時的出現的問題,在後面代碼Debug的時候花費了至關多的時間,在給出的PSP表中能夠看出,本來預計120分鐘要完成的測試(自我測試,修改代碼,提交修改)項中,實際花費時間拉長到了980分鐘。

最後這次做業原本在規劃時劃撥了完整的一天時間進行代碼的優化,可是需求一直不斷在變化,天天在完成新功能的同時還要對以前完成的部分進行修改以適應新的需求.並且因爲許多需求此前定義不明確致使咱們在需求分析時考慮的狀況不夠貼近實際,在編碼時發現此前的需求分析及功能設計不能知足須要,須要從新構建,而且代碼也得推倒重寫,浪費了大量時間

並且因爲需求定義不明,致使咱們對部分狀況的處理結果進行了討論並定義當前程序存在BUG,可是等到咱們花費大量時間查找文件內容並定位到具體致使BUG緣由,隨後又花費必定時間進行修復,擠佔了本就爲數很少的代碼測試時間,結果一覺醒來發現作了無用功?

隊友互評

在此次的結對任務中,個人隊友起到了一箇中流砥柱的做用。他不只有很強的代碼開發能力,編寫的代碼思路清晰,對咱們後面的代碼測試、修改有很大幫助。並且在溝通交流中能夠清晰的表達本身的想法思路,同時又能快速理解我所表達的思路觀點,所以在溝通交流上能夠說是配合的至關愉快。除此以外,他還擁有很強的自學能力,可以在短期內學習爬蟲技術,並編寫爬蟲程序實現對給定網站論文信息的爬取。最後,在後期的代碼測試、Debug部分,會廢寢忘食的修改錯誤代碼。針對代碼優化部分,他老是能想出優秀的方案來實現對代碼的優化,提升程序的效率。能夠說是有能力還肝,我要向他看齊。

相關文章
相關標籤/搜索