1. 程式人生 > >軟工實踐第五次作業(結對第二次作業)

軟工實踐第五次作業(結對第二次作業)

navig 但是 measure pat air conn map排序 demo 雷達

原博客
隊友博客
github項目地址

目錄

  • 具體分工
  • 需求分析
  • PSP表格
  • 解題思路描述與設計實現說明
    • 爬蟲使用
    • 代碼組織與內部實現設計(類圖)
    • 算法的關鍵與關鍵實現部分流程圖
  • 附加題設計與展示
    • 設計的創意獨到之處
    • 實現思路
    • 實現成果展示
  • 關鍵代碼解釋
  • 性能分析與改進
  • 單元測試
  • Github的代碼簽入記錄
  • 遇到的代碼模塊異常或結對困難及解決方法
  • 評價隊友
  • 學習進度條

具體分工

    • 字符、有效行、單詞數目統計,單詞詞頻統計
    • 自定義輸入輸出文件
    • 新增詞組詞頻統計功能
    • 自定義詞頻統計輸出
    • 多參數的混合使用
    • 博客編輯排版
    • 效能分析
  • 隊友
    • 使用工具爬取論文信息
    • 加入權重的詞頻統計
    • 單元測試

需求分析

  • 基本功能
    • 使用工具爬取論文信息,輸出到result.txt
    • 統計文件的字符數
    • 統計文件的單詞數
    • 統計文件的有效行數
    • 按照規定格式輸出結果至文件result.txt
  • 進階功能
    • 自定義輸入輸出文件
    • 加入權重的詞頻統計
    • 新增詞組詞頻統計功能
    • 自定義詞頻統計輸出
    • 多參數的混合使用

PSP表格

Personal Software Process Stages 預估耗時(分) 實際耗時(分)
Planning 計劃 50 60
Estimate · 估計這個任務需要多少時間 50 60
Development 開發 1490 1520
Analysis · 需求分析(包括學習新技術) 500 480
Design Spec · 生成設計文檔 30 40
Design Review · 設計復審 30 50
Coding Standard · 代碼規範 (為目前的開發制定合適規範) 30 35
Design · 具體設計 300 300
Coding · 具體編碼 600 620
Code Review · 代碼復審 200 190
Test · 測試(自我測試,修改代碼,提交修改) 200 215
Reporting 報告 50 60
Test Repor · 測試報告 40 45
Size Measurement · 計算工作量 5 10
Postmortem & Process Improvement Plan · 事後總結, 並提出過程改進計劃 5 5
合計 1590 1640

解題思路描述與設計實現說明

  • 爬蟲使用

    我們的爬蟲是由java來實現的。
    代碼部分如下:
    主要分為三個部分:
    • 第一個部分主函數,用於輸入與輸出:
      使用Jsoup方法,將爬取出的源代碼轉換成document類。再通過篩選標簽dt[class=ptitle]尋找子頁的連接,對子網頁進行二次爬取,再將爬取的代碼進行篩選,得到所需的文本,生成result.txt。
      public static void main(String[] args) {
          int number=0;
          int i=0;
          String code=geturlcode(url);
          String code1=null;
          //將源代碼轉換為Doc
          Document doc= Jsoup.parse(code);
          //篩選子網頁連接
          Elements ele=(doc).select("dt[class=ptitle]");
          number=ele.size();
          File f=new File("result.txt");
          try {
              PrintWriter output = new PrintWriter(f);
          for(Element link : ele){
              String test="http://openaccess.thecvf.com/";
              link=link.child(1);
              test=test+link.attr("href");
              String text =null;
              //讀取子網頁內容
              text=geturlcode(test);
              text=gettext(text);
                  output.print(i);
                  i++;
                  output.print("\r\n");
                  output.print(text);
          }
          output.close();
          }catch (Exception e) {
              System.out.println("寫文件錯誤");
          }
          System.out.println("成功");
    
      }
    • 第二個部分文本篩選:
      通過標簽與
    //篩選標題與摘要
      public static String gettext(String code)
      {
          String a,b,c;
          Document doc=Jsoup.parse(code);
          Elements ele1=(doc).select("div[id=papertitle]");
          a=ele1.text();
          a=a.replace("/n","");
          Elements ele2=(doc).select("div[id=abstract]");
          b=ele2.text();
          b=b.replace("/n","");
          c="Title: "+a+"\r\n"+"Abstract: "+b+"\r\n"+"\r\n"+"\r\n";
          return c;
      }
    • 第三個部分網頁源代碼的獲取:
      爬取的方式通過java自帶的URL方法,建立連接,讀取網頁的源代碼,返回代碼。
      //爬取網頁源代碼
      public static String geturlcode(String url) {
          //定義url
          URL newurl = null;
          //定義連接
          URLConnection urlcon = null;
          //定義輸入
          InputStream input = null;
          //定義讀取
          InputStreamReader reader = null;
          //定義輸出
          BufferedReader breader = null;
          StringBuilder code = new StringBuilder();
          try {
              //獲取地址
              newurl = new URL(url);
              //獲取連接
              urlcon = newurl.openConnection();
              //獲取輸入
              input = urlcon.getInputStream();
              //讀取輸入
              reader = new InputStreamReader(input);
              //輸出
              breader = new BufferedReader(reader);
              String temp = null;
              while ((temp = breader.readLine()) != null) {
                  code.append(temp + "/n");
              }
    
          } catch (MalformedURLException e) {
              e.printStackTrace();
          } catch (IOException e) {
              e.printStackTrace();
          }
          return code.toString();
      }
    }
  • 代碼組織與內部實現設計(類圖)

    • 類圖
      技術分享圖片
    • 流程圖
      技術分享圖片
  • 算法的關鍵與關鍵實現部分流程圖

    • 詞組詞頻統計功能的實現
      技術分享圖片
    • 判斷詞組是否符合要求
      技術分享圖片

附加題設計與展示

  • 設計的創意獨到之處

  • 實現思路

    ??
  • 實現成果展示

    ???

關鍵代碼解釋

  • 控制臺傳參
    遍歷args,根據args[i]的值取args[i+1]的值處理。
for(int i=0;i<args.length;i+=2){
            switch(args[i]){
                /*-w 參數設定是否采用不同權重計數*/
                case "-w":
                    if(Integer.parseInt(args[i+1])==1){
                        System.out.println("采用權重計數。");
                        cntByWeight=true;
                    }else{
                        System.out.println("不采用權重計數。");
                        cntByWeight=false;
                    }
                    break;
                /*-i 參數設定讀入文件的存儲路徑*/
                case "-i":
                    inPathname=args[i+1];
                    System.out.println("讀入文件路徑為"+inPathname+"。");
                    break;
                /*-o 參數設定生成文件的存儲路徑*/
                case "-o":
                    outPathname=args[i+1];
                    System.out.println("生成文件路徑為"+outPathname+"。");
                    break;
                /*-m 參數設定統計的詞組長度*/
                /*使用詞組詞頻統計功能時,不再統計單詞詞頻,而是統計詞組詞頻,但不影響單詞總數統計*/
                /*未出現 -m 參數時,不啟用詞組詞頻統計功能,默認對單詞進行詞頻統計*/
                case "-m":
                    phraseLength=Integer.parseInt(args[i+1]);
                    System.out.println("采用詞組詞頻統計功能,詞組長度為"+phraseLength+"。");
                    break;
                /*-n 參數設定輸出的單詞數量*/
                /*未出現 -n 參數時,不啟用自定義詞頻統計輸出功能,默認輸出10個*/
                case "-n":
                    needNum=Integer.parseInt(args[i+1]);
                    System.out.println("輸出的單詞/詞組數量為"+needNum+"。");
                    break;

            }
        }
  • 字符統計
    用bufferedreader.readline()按行讀取每一個非空且非論文編號的字符個數,每行字符個數額外+1(換行符算一個),每讀完一篇論文將總字符數-17("abstract: "+"title: ")。
BufferedReader br = new BufferedReader(new FileReader(file));
                while (br.readLine() != null) {
                    try {
                        while ((sb = br.readLine()) != null) {
                            if (sb.length() == 0) {
                                break;
                            }
                            characters += (sb.length() + 1);
                        }
                        characters -= 17;
                        br.readLine();
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            read.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
  • 單詞統計
    用bufferedreader.readline()按行讀取,每讀完一篇論文將總單詞數-2("abstract"+"title")。文件內容用.split()方法分詞並統計符合要求的個數。
                StringBuilder sb = new StringBuilder();
                while (br.readLine() != null) {
                    String temp = null;
                    try {
                        while ((temp = br.readLine()) != null) {
                            if (temp.length() == 0) {
                                break;
                            }
                            sb.append(temp);
                            sb.append(" ");//每行結束多讀一個空格
                        }
                        words -= 2;
                        br.readLine();
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            read.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
                ……
  • 有效行統計
    每讀一行行數+1,論文編號行和空白行除外。
代碼略。
  • 生成新文件
    以之前各統計函數的返回值為參數在指定路徑生成新txt。
代碼略。
  • map用value排序
    map用list存儲然後使用sort()方法排序。
    public static Map<String, String> sortMapByValue(Map<String, String> oriMap) {
        if (oriMap == null || oriMap.isEmpty()) {
            return null;
        }
        Map<String, String> sortedMap = new LinkedHashMap<String, String>();
        List<Map.Entry<String, String>> entryList = new ArrayList<Map.Entry<String, String>>(oriMap.entrySet());
        entryList.sort(new MapValueComparator());
        Iterator<Map.Entry<String, String>> iter = entryList.iterator();
        Map.Entry<String, String> tmpEntry = null;
        while (iter.hasNext()) {
            tmpEntry = iter.next();
            sortedMap.put(tmpEntry.getKey(), tmpEntry.getValue());
        }
        return sortedMap;
    }
  • 比較器Comparator
    之前的比較器有重大bug,直接把String拿去比較了,這次修改的時候才發現>_<。
public class MapValueComparator implements Comparator<Map.Entry<String, String>> {
    @Override
    /*負整數:當前對象的值 < 比較對象的值 , 位置排在前
    * 零:當前對象的值 = 比較對象的值 , 位置不變
    *正整數:當前對象的值 > 比較對象的值 , 位置排在後
    */
    public int compare(Map.Entry<String, String> me1, Map.Entry<String, String> me2) {
        int flag=0;
        if(Long.parseLong(me2.getValue()) > Long.parseLong(me1.getValue())){
            flag=1;
        }else if(Long.parseLong(me1.getValue()) > Long.parseLong(me2.getValue())){
            flag=-1;
        }else if(Long.parseLong(me1.getValue()) == Long.parseLong(me2.getValue())){
            flag=me1.getKey().compareTo(me2.getKey());
        }
        return flag;
    }
}
  • 去除數組中所有空值
    用StringBuffer來存放數組中的非空元素,用“;”分隔,用String的split方法分割,得到數組。
    public static String[] replaceNull(String[] str) {
        //用StringBuffer來存放數組中的非空元素,用“;”分隔
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < str.length; i++) {
            if ("".equals(str[i])) {
                continue;
            }
            sb.append(str[i]);
            if (i != str.length - 1) {
                sb.append(";");
            }
        }
        //用String的split方法分割,得到數組
        str = sb.toString().split(";");
        return str;
    }
  • 詞頻統計
    主要就是先記錄文件為一個String,然後用分隔符切割成String[],並保留分隔符,根據詞組長度篩選符合條件的詞組,根據是否以權重統計記入map,然後排序。
    代碼較長,建議看流程圖更加直觀。
    public static Map<String, String> countPhraseFrequency(String pathname, long phraseLength,boolean cntByWeight) {
        Map<String, String> map = new HashMap<String, String>();
        boolean flag = false;
        try {
            String encoding = "UTF-8";
            File file = new File(pathname);
            if (file.isFile() && file.exists()) {
                InputStreamReader read = new InputStreamReader(new FileInputStream(file), encoding);
                /*讀取文件數據*/
                StringBuffer sb = null;
                BufferedReader br1;
                try {
                    br1 = new BufferedReader(new FileReader(file));
                    String temp = br1.readLine();
                    sb = new StringBuffer();
                    while (temp != null) {
                        sb.append(temp);
                        sb.append(" ");//每行結束多讀一個空格
                        temp = br1.readLine();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                /*讀取的內容*/
                String info = null;
                if (sb != null) {
                    info = sb.toString();
                }
                /*保留分隔符*/
                Pattern p =Pattern.compile("[^a-zA-Z0-9]");
                Matcher m = null;
                String s[] = new String[0];
                if (info != null) {
                    m = p.matcher(info);
                    s = info.split("[^a-zA-Z0-9]");
                }
                if(s.length > 0)
                {
                    int count = 0;
                    while(count < s.length)
                    {
                        if(m.find())
                        {
                            s[count] += m.group();
                        }
                        count++;
                    }
                }
                s = replaceNull(s);
                /*統計單詞個數*/
                for (int i = 0; i < s.length; i++) {
                    StringBuilder content = new StringBuilder();
                    int cnt=0;
                    for (int j = i; cnt<phraseLength&&j<s.length; j++) {
                        if (s[j].length() >= 4 && !s[j].toLowerCase().equals("title:") && !s[j].toLowerCase().equals("abstract:")) {
                            String temp = s[j].substring(0, 4);
                            temp = temp.replaceAll("[^a-zA-Z]", "");
                            if (temp.length() >= 4) {
                                cnt++;
                                if(cnt==phraseLength){
                                    content.append(s[j].substring(0,s[j].length()-1));
                                }else{
                                    content.append(s[j]);
                                }
                            } else {
                                break;
                            }
                        } else if (s[j].toLowerCase().equals("title:")) {
                            flag = true;
                            break;
                        } else if (s[j].toLowerCase().equals("abstract:")) {
                            flag = false;
                            break;
                        } else if(s[j].matches("[^a-zA-Z0-9]")&&cnt>=1){
                            content.append(s[j]);
                        }else{
                            break;
                        }
                        if (cnt==phraseLength) {
                            String phrase = content.toString();
                            if (flag && cntByWeight) {
                                if (map.containsKey(phrase.toLowerCase())) {//判斷Map集合對象中是否包含指定的鍵名
                                    map.put(phrase.toLowerCase(), Integer.parseInt(map.get(phrase.toLowerCase())) + 10 + "");
                                } else {
                                    map.put(phrase.toLowerCase(), 10 + "");
                                }
                            } else {
                                if (map.containsKey(phrase.toLowerCase())) {//判斷Map集合對象中是否包含指定的鍵名
                                    map.put(phrase.toLowerCase(), Integer.parseInt(map.get(phrase.toLowerCase())) + 1 + "");
                                } else {
                                    map.put(phrase.toLowerCase(), 1 + "");
                                }
                            }
                        }
                    }
                }
                /*map排序*/
                map = sortMapByValue(map);
                read.close();
            } else {
                System.out.println("找不到指定的文件");
            }
        } catch (Exception e) {
            System.out.println("讀取文件內容出錯");
            e.printStackTrace();
        }
        return map;
    }

性能分析與改進

  • 改進的思路

    • 多線程
      參考大佬的代碼把原來Main函數中順序執行的計數函數改為建立線程池,並行運行三個計數函數,詞頻統計由於開銷較大所以獨立運行。
    ExecutorService executor = Executors.newCachedThreadPool();
          //計算字符數
          String finalInPathname = inPathname;
          Future<Long> futureChar = executor.submit(() -> lib.countChar(finalInPathname));
          //計算單詞數
          Future<Long> futureWord = executor.submit(() -> lib.countWord(finalInPathname));
          //計算行數
          Future<Long> futureLine = executor.submit(() -> lib.countLines(finalInPathname));
    • 簡化代碼
      之前寫的較匆忙,有很多地方冗余或存在風險,在不改變功能的情況下進行了精簡和錯誤預防。
      如:
                  info = sb.toString();

    改為

                  if (sb != null) {
                      info = sb.toString();
                  }
    • 其他改動
      原先記錄文檔內容采用的是用StringBuffer,後來與StringBuilder做了比較,相比來說StringBuilder更快,雖然在多線程環境下不能保證線程安全,但線程池中只有一個線程有用到StringBuilder,因此還是選擇了速度更快的StringBuilder替代StringBuffer。
      對於分割字符串,我最初是準備StringTokenizer替代.splite()方法。但是String.Split()使用正則表達式,而StringTokenizer的只是使用逐字分裂的字符。因此如果我想用更復雜的邏輯比單個字符(如 r n分割)來標記一個字符串,所以還是選擇String.Split() 。
  • 性能分析圖和程序中消耗最大的函數

    技術分享圖片 技術分享圖片 技術分享圖片

    從圖中可以看出BufferedReader的readline方法和String的split、replaceAll方法占了主要開銷,消耗最大的函數是詞頻統計函數。

單元測試

技術分享圖片
代碼如下:

public void countChar() {
    System.out.println("count Char1");
    long r=lib.countChar("C:\\Users\\Administrator\\Desktop\\test\\test2.txt");
    assertEquals(1040,r);
    System.out.println("count Char2");
    r=lib.countChar("C:\\Users\\Administrator\\Desktop\\test\\test.txt");
    assertEquals(2914,r);
    System.out.println("count Char3");
    r=lib.countChar("C:\\Users\\Administrator\\Desktop\\test\\test3.txt");
    assertEquals(418,r);
}
@Test
public void countWord() {
    System.out.println("count Word1");
    long w=lib.countWord("C:\\Users\\Administrator\\Desktop\\test\\test2.txt");
    assertEquals(208,w);
    System.out.println("count Word2");
    w=lib.countWord("C:\\Users\\Administrator\\Desktop\\test\\test.txt");
    assertEquals(287,w);
    System.out.println("count Word3");
    w=lib.countWord("C:\\Users\\Administrator\\Desktop\\test\\test3.txt");
    assertEquals(42,w);
}
@Test
public void countPhraseFrequency() {
    System.out.println("count Phrase1");
    String f;
    StringBuilder content = new StringBuilder("");
   int i=1;
    Map<String, String> t=lib.countPhraseFrequency("C:\\Users\\Administrator\\Desktop\\test\\test2.txt",5,true);
    Set<String> keys = t.keySet();
    for (String key : keys) {
        content.append("<").append(key).append(">:").append(t.get(key));
        i++;
        if (i > 5)
            break;
        content.append("\r\n");
    }
    f=content.toString();
    assertEquals("<aaaa aaaa aaaa aaaa aaaa>:192\r\n",f);
    System.out.println("count Phrase2");
    i=1;
    content = new StringBuilder("");
    t=lib.countPhraseFrequency("C:\\Users\\Administrator\\Desktop\\test\\test.txt",5,true);
    keys = t.keySet();
    for (String key : keys) {
        content.append("<").append(key).append(">:").append(t.get(key));
        i++;
        if (i > 5)
            break;
        content.append("\r\n");
    }
    f=content.toString();
    assertEquals("<wild with generative adversarial network>:10\r\n" +
            "<clear high-resolution face from>:2\r\n" +
            "<active perception, goal-driven navigation>:1\r\n" +
            "<agent must first intelligently navigate>:1\r\n" +
            "<challenging dataset wider face demonstrate>:1",f);
    System.out.println("count Phrase3");
    i=1;
    content = new StringBuilder("");
    t=lib.countPhraseFrequency("C:\\Users\\Administrator\\Desktop\\test\\test3.txt",4,true);
    keys = t.keySet();
    for (String key : keys) {
        content.append("<").append(key).append(">:").append(t.get(key));
        i++;
        if (i > 4)
            break;
        content.append("\r\n");
    }
    f=content.toString();
    assertEquals("<active perception, goal-driven>:1\r\n" +
            "<commonsense reasoning, long-term>:1\r\n" +
            "<driven navigation, commonsense reasoning>:1\r\n" +
            "<goal-driven navigation, commonsense>:1",f);
}


@Test
public void countLines() {
    System.out.println("count Lines");
    long l=lib.countLines("C:\\Users\\Administrator\\Desktop\\test\\test2.txt");
    assertEquals(4,l);
    System.out.println("count Lines");
    l=lib.countLines("C:\\Users\\Administrator\\Desktop\\test\\test.txt");
    assertEquals(6,l);
    System.out.println("count Lines");
    l=lib.countLines("C:\\Users\\Administrator\\Desktop\\test\\test3.txt");
    assertEquals(2,l);
}

主要對字符統計,行數統計,單詞統計,詞組詞頻排序進行測試,每個單元用三個樣例進行測試。

Github的代碼簽入記錄

技術分享圖片
技術分享圖片
技術分享圖片
由於使用github不熟,所以代碼簽入比較混亂,commit也比較隨意,以後會慢慢改進的。

遇到的代碼模塊異常或結對困難及解決方法

  • map排序及統計異常
    這個是上次作業遺留的問題了,我為什麽這麽晚才發現.>_<.!!!之前存map的時候,雖然計算的時候會轉為long計算,但是儲存value的值是String格式。寫比較器的時候忽略了這點直接使用了compareTo方法,後來隨手一測發現不對,3居然排在101上面。一開始以為是計數邏輯的問題,找了好久才發現原來是比較器寫錯了。把value值轉為String比較後問題解決。
  • 進程結束異常
    程序運行完後過一段時間進程才會結束,後來發現是忘記把executor對象shutdown了。
  • 結對困難
    國慶兩個人都要回家所以在前一天晚上制定了工作計劃,國慶期間很完美的完成了,幾乎沒遇到什麽困難。

    評價隊友

  • 值得學習的地方

    爬蟲學得很快,代碼註釋很完整。
    對於我寫的亂七八糟的代碼居然能看得懂並在上面加新功能。
    對於工作認真負責。
  • 需要改進的地方

    彼此交流不足。

    學習進度條

  • 雷達圖
    技術分享圖片
  • 進度表
    技術分享圖片

回到頂部

軟工實踐第五次作業(結對第二次作業)