軟工實踐第五次作業(結對第二次作業)
阿新 • • 發佈:2018-10-12
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了。 結對困難
國慶兩個人都要回家所以在前一天晚上制定了工作計劃,國慶期間很完美的完成了,幾乎沒遇到什麽困難。評價隊友
值得學習的地方
爬蟲學得很快,代碼註釋很完整。
對於我寫的亂七八糟的代碼居然能看得懂並在上面加新功能。
對於工作認真負責。需要改進的地方
彼此交流不足。
學習進度條
- 雷達圖
進度表
回到頂部
軟工實踐第五次作業(結對第二次作業)