這個作業屬于哪個課程 | https://edu.cnblogs.com/campus/fzu/2020SpringW |
---|---|
這個作業要求在哪裡 | https://edu.cnblogs.com/campus/fzu/2020SpringW/homework/10281 |
這個作業的目标 | 考察需求分析,學習git、github |
作業正文 | https://www.cnblogs.com/herokilito/p/12264891.html |
其他參考文獻 | ... |
1、GitHub
-
GitHub使用者名
herokilito -
本次作業倉庫位址
https://github.com/herokilito/InfectStatistic-main -
第一次作業相關倉庫
-
Spring
簡介:MyBatis-Spting擴充卡,會幫助你将 MyBatis 代碼無縫地整合到 Spring 中。
-
Spring-framework
簡介:Spring架構,Spring提供了Java程式設計語言以外的所有所需内容,可用于為各種場景和體系結建構立企業應用程式。
-
mybatis-3
簡介:對象關系映射工具,簡單性是MyBatis資料映射器的最大優勢。
-
Spring-boot
簡介:Spring Boot使建立具有Spring動力的生産級應用程式和服務變得非常容易。
-
SpringCloudLearning
簡介:SpringCloud教程
-
2、PSP表格
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | ||
Estimate | 估計這個任務需要多少時間 | 1200 | 1330 |
Development | 開發 | ||
Analysis | 需求分析 (包括學習新技術) | 300 | 200 |
Design Spec | 生成設計文檔 | 60 | 80 |
Design Review | 設計複審 | 10 | 20 |
Coding Standard | 代碼規範 (為目前的開發制定合适的規範) | 30 | |
Design | 具體設計 | 150 | 100 |
Coding | 具體編碼 | 420 | 540 |
Code Review | 代碼複審 | ||
Test | 測試(自我測試,修改代碼,送出修改) | 120 | |
Reporting | 報告 | ||
Test Report | 測試報告 | ||
Size Measurement | 計算工作量 | ||
Postmortem & Process Improvement Plan | 事後總結, 并提出過程改進計劃 | ||
合計 |
3、分析與設計
-
思路描述
- 首先分離參數,判斷傳入了什麼參數,記錄各個參數的參數值。
- 每種參數都設計一個方法來實作相應的功能。
- 根據記錄的參數按特定的順序一個個執行相應的方法。
- 在執行方法的過程發生未預期的錯誤直接結束程序并顯示錯誤資訊。
- 資料可以用鍵值對的方式儲存,如java中的map。
-
程式設計
-
代碼組織
-
方法劃分
有五種參數,根據參數類型劃分成五個方法
-log參數:dolog()
-out參數:doOut()
-date參數:doDate()
-type參數:doType()
-province參數:doProvince()
日志中可能出現八種情況,根據不同情況劃分不同的計算方法
<省> 新增 感染患者 n人:increaseInf()
<省> 新增 疑似患者 n人:increaseSus()
<省1> 感染患者 流入 <省2> n人:infInflow()
<省1> 疑似患者 流入 <省2> n人:susInflow()
<省> 死亡 n人:dead()
<省> 治愈 n人:cure()
<省> 疑似患者 确診感染 n人:diagnose()
<省> 排除 疑似患者 n人:exclude()
-
資料結構
觀察預期輸出資料可知,使用某種資料結構即能儲存省份名,又能儲存省份資料便于簡化程式設計,于是使用了java的Map接口。
因為輸出資料需要按拼音資料排序,于是選擇了LinkedHashMap執行個體。
LinkedHashMap<String,List> String為省份名,List為資料清單儲存該省份四種資料,每個資料項用整型存儲。
-
-
主要函數流程圖
2020寒假作業(2/2)
-
4、主要代碼
-
代碼說明
- 開發語言:Java
- JDK版本:1.8
- 開發環境:IDEA
-
代碼規範
https://github.com/herokilito/InfectStatistic-main/blob/master/221701328/codestyle.md -
主要代碼展示及說明
public void execute(String[] args) throws Lib.Exit { if(args.length == 1){ Lib.helpList(); //顯示提示資訊 throw new Lib.Exit("請按照提示輸入指令"); } /*分離參數*/ int i = 1; while (i < args.length) { switch (args[i]) { case "-log": hasLog = true; if (++i >= args.length) { //如果-log後面沒有給參數值 throw new Lib.Exit("-log參數缺少參數值"); } logParam = args[i++]; //-log後面跟着的參數為-log的參數值 break; case "-out": hasOut = true; if (++i >= args.length) { //如果-out後面沒有給參數值 throw new Lib.Exit("-out參數缺少參數值"); } outParam = args[i++]; //-out後面跟着的參數為-out的參數值 break; case "-date": hasDate = true; if (++i >= args.length) { //如果-date後面沒有給參數值 throw new Lib.Exit("-date參數缺少參數值"); } dateParam = args[i++]; //-date後面跟着的參數為-date的參數值 break; case "-type": hasType = true; while (++i < args.length && !args[i].equals("-log") && !args[i].equals("-out") && !args[i].equals("-date") && !args[i].equals("-province")) { //-type的參數值範圍 typeParams.add(args[i]); } break; case "-province": hasProvince = true; while (++i < args.length && !args[i].equals("-log") && !args[i].equals("-out") && !args[i].equals("-date") && !args[i].equals("-type")) { //-province的參數值範圍 provinceParams.add(args[i]); } break; default: throw new Lib.Exit("\"" + args[i] + "\"無法解析的參數"); } } /*執行相應的方法*/ if(!hasLog){ //log必須有 throw new Lib.Exit("缺少-log參數"); } if(!hasOut){ //out必須有 throw new Lib.Exit("缺少-out參數"); } if(!hasDate){ //如果沒有data參數 dateParam=new SimpleDateFormat("yyyy-MM-dd").format(new Date()); //目前日期 } doLog(logParam); //讀取日志路徑 doDate(dateParam); //讀取日志路徑下相應日期的日志 if(hasType){ doType(typeParams); //需要輸出的資訊類型 } if(hasProvince){ doProvince(provinceParams); //需要輸出的省份疫情資訊 } doOut(outParam); //輸出到指定的路徑 }
說明:判斷傳入的參數、參數值,調用相應的方法,具體請看程式注釋。
private void doDate(String date) throws Lib.Exit { List<File> logList = Lib.getLogFiles(logDirectory); DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); Date paramDate; BufferedReader reader = null; try { paramDate = dateFormat.parse(date); List<Integer> nationalData = statistics.get("全國"); //全國資料 for (File log : logList) { Date logDate = dateFormat.parse(log.getName().substring(0, log.getName().indexOf('.'))); if(logDate.compareTo(paramDate) > 0) { //判斷日志檔案的日期是否小于等于給定日期 continue; } reader = new BufferedReader(new InputStreamReader(new FileInputStream(log), StandardCharsets.UTF_8)); String dataRow; while((dataRow = reader.readLine()) != null){ if(dataRow.startsWith("//")) { //忽略注釋行 continue; } String[] data = dataRow.split(" "); //分割資料行 if(!outProvince.contains(data[0])){ outProvince.add(data[0]); } List<Integer> provinceData = statistics.get(data[0]); //目前行的省份資料 List<Integer> destProvince; //用于處理流入 switch (data[1]) { case INCREMENT: //處理新增 if (data[2].equals(INFECTION_PATIENT)) { //新增感染 increaseInf(nationalData, provinceData, Lib.parseData(data[3])); } else { //新增疑似 increaseSus(nationalData, provinceData, Lib.parseData(data[3])); } break; case EXCLUDE: //處理排除疑似 excludeSus(nationalData, provinceData, Lib.parseData(data[3])); break; case CURE: //處理治愈 cure(nationalData,provinceData,Lib.parseData(data[2])); break; case DEAD: //處理死亡 dead(nationalData,provinceData,Lib.parseData(data[2])); break; case INFECTION_PATIENT: //處理感染患者流入 destProvince = statistics.get(data[3]); infInflow(provinceData,destProvince,Lib.parseData(data[4])); break; case SUSPECTED_PATIENT: if(data[2].equals(INFLOW)){ //處理疑似患者流入 destProvince = statistics.get(data[3]); susInflow(provinceData,destProvince,Lib.parseData(data[4])); } else if(data[2].equals(DIAGNOSE)) { //處理确診 diagnose(nationalData,provinceData,Lib.parseData(data[3])); } break; } } } }catch (Exception e){ throw new Lib.Exit(e.getMessage()); }finally { try{ if (reader != null) { reader.close(); } }catch (Exception e){ e.printStackTrace(); } } }
說明:讀取日志資訊,判斷日志内容屬于哪一類,并調用相應的計算方法,具體請看程式注釋。
5、單元測試,單元測試覆寫率
-
單元測試說明
單元測試所用工具為IDEA的Junit5插件,測試所用日志資料為作業模闆給的三個日志檔案 -
單元測試展示及測試結果
@Test void testLogOut1() { String[] args = { "list" , "-log" , "D:/Java/InfectStatistic-main/test/log" , "-out" , "D:/Java/InfectStatistic-main/test/result/listOut1.txt" , }; InfectStatistic.main(args); }
說明:基本參數的測試,隻有-log和-out參數![]()
2020寒假作業(2/2)
@Test void testLogOut2() { String[] args = { "list" , "-out" , "D:/Java/InfectStatistic-main/test/result/listOut2.txt" , "-log" , "D:/Java/InfectStatistic-main/test/log" , }; InfectStatistic.main(args); }
說明:基本參數的測試,測試參數順序是否會影響結果![]()
2020寒假作業(2/2)
@Test void testDate1() { String[] args = { "list" , "-log" , "D:/Java/InfectStatistic-main/test/log" , "-out" , "D:/Java/InfectStatistic-main/test/result/listOut3.txt" , "-date" , "2020-01-22" }; InfectStatistic.main(args); }
說明:-date參數的測試,測試日期在日志檔案之内![]()
2020寒假作業(2/2)
@Test void testDate2() { String[] args = { "list" , "-log" , "D:/Java/InfectStatistic-main/test/log" , "-out" , "D:/Java/InfectStatistic-main/test/result/listOut4.txt" , "-date" , "2020-2-1" }; InfectStatistic.main(args); }
說明:-date參數的測試,測試日期在日志檔案之外![]()
2020寒假作業(2/2)
@Test void testType1() { String[] args = { "list" , "-log" , "D:/Java/InfectStatistic-main/test/log" , "-out" , "D:/Java/InfectStatistic-main/test/result/listOut5.txt" , "-type" , "ip" , "sp" , "cure" , "dead" , }; InfectStatistic.main(args); }
說明:測試-type參數,包含全部-type參數值![]()
2020寒假作業(2/2)
@Test void testType2() { String[] args = { "list" , "-log" , "D:/Java/InfectStatistic-main/test/log" , "-out" , "D:/Java/InfectStatistic-main/test/result/listOut6.txt" , "-type" , "cure" , "dead" , "ip" , }; InfectStatistic.main(args); }
說明:測試-type參數,包含部分-type參數值且參數值順序改變![]()
2020寒假作業(2/2)
@Test void testProvince1() { String[] args = { "list" , "-log" , "D:/Java/InfectStatistic-main/test/log" , "-out" , "D:/Java/InfectStatistic-main/test/result/listOut7.txt" , "-province" , "全國" , "福建" , "湖北" , }; InfectStatistic.main(args); }
說明:測試-province參數![]()
2020寒假作業(2/2)
@Test void testProvince2() { String[] args = { "list" , "-log" , "D:/Java/InfectStatistic-main/test/log" , "-out" , "D:/Java/InfectStatistic-main/test/result/listOut8.txt" , "-province" , "全國" , "浙江" , "福建" , }; InfectStatistic.main(args); }
![]()
2020寒假作業(2/2)
@Test void testAll1() { String[] args = { "list" , "-log" , "D:/Java/InfectStatistic-main/test/log" , "-out" , "D:/Java/InfectStatistic-main/test/result/listOut9.txt" , "-date" , "2020-01-27" , "-type" , "ip" , "sp" , "dead" , "cure" , "-province" , "福建" , "浙江" , "河北" , "湖北" }; InfectStatistic.main(args); }
說明:測試全部參數![]()
2020寒假作業(2/2)
@Test void testAll2() { String[] args = { "list" , "-log" , "D:/Java/InfectStatistic-main/test/log" , "-out" , "D:/Java/InfectStatistic-main/test/result/listOut10.txt" , "-date" , "2020-01-23" , "-type" , "ip" , "dead" ,"cure" , "-province" , "福建" , "浙江" , "河北" , "湖北" }; InfectStatistic.main(args); }
![]()
2020寒假作業(2/2)
@Test void testHelp() { String[] args = { }; InfectStatistic.main(args); }
說明:測試提示資訊![]()
2020寒假作業(2/2)
@Test void testListHelp() { String[] args = { "list" , }; InfectStatistic.main(args); }
說明:測試list指令的提示資訊![]()
2020寒假作業(2/2)
@Test void testUnknownCmdError() { String[] args = { "listt" , "-log" , "D:/Java/InfectStatistic-main/test/log" , "-out" , "D:/Java/InfectStatistic-main/test/result/listOut11.txt" , }; InfectStatistic.main(args); }
說明:測試錯誤指令![]()
2020寒假作業(2/2)
@Test void testUnknownParamError() { String[] args = { "list" , "-loge" , "D:/Java/InfectStatistic-main/test/log" , "-out" , "D:/Java/InfectStatistic-main/test/result/listOut12.txt" , }; InfectStatistic.main(args); }
說明:測試list指令的錯誤參數![]()
2020寒假作業(2/2)
@Test void testLackParamError1() { String[] args = { "list" , "-log" , "D:/Java/InfectStatistic-main/test/log" , "-out" , }; InfectStatistic.main(args); }
說明:測試必要參數缺少參數值![]()
2020寒假作業(2/2)
@Test void testLackParamError2() { String[] args = { "list" , "-log" , "D:/Java/InfectStatistic-main/test/log" , }; InfectStatistic.main(args); }
說明:測試缺少必要參數![]()
2020寒假作業(2/2)
@Test void testUnknownType() { String[] args = { "list" , "-log" , "D:/Java/InfectStatistic-main/test/log" , "-out" , "D:/Java/InfectStatistic-main/test/result/listOut13.txt" , "-type" , "确診" , "sp" , "cure" , }; InfectStatistic.main(args); }
說明:測試-type參數的錯誤參數值![]()
2020寒假作業(2/2)
-
單元測試覆寫率
![]()
2020寒假作業(2/2)
6、性能測試,性能優化
-
性能測試截圖及說明
-
說明
性能測試用了2020-01-20到2010-01-31共十二個日志檔案,每個日志檔案近萬條資料,共約十二萬條日志資料
使用Jprofiler11進行分析
-
測試結果
2020寒假作業(2/2) 2020寒假作業(2/2)
-
-
性能優化
-
分析
由測試結果可以看出程式再doDate()這個方法耗時最久,于是優先優化這個方法。
發現我在處理每一條日志資料的時候都計算了全國資料,這沒有必要,隻需要輸出的時候計算全國資料就可以。于是我修改了計算方法,并增加了一個用于計算全國資料的方法。
private void countNational() { List<Integer> national = statistics.get("全國"); for (List<Integer> data : statistics.values()) { for (int i = 0 ; i < national.size() ; i ++){ national.set(i,national.get(i) + data.get(i)); } }
這個方法在doOut()輸出之前調用。
doDate()是一個個讀取log檔案,計算完一個檔案再讀取下一個,我想為何不能用多線程來完成這一步驟呢?把讀取檔案内容并計算的任務交給線程去處理,充分發揮多核處理器的并發性能。
threadPool.submit(() -> { //建立新線程,加入線程池 BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(new FileInputStream(log), StandardCharsets.UTF_8)); String dataRow; while ((dataRow = reader.readLine()) != null) { if (dataRow.startsWith("//")) { //忽略注釋行 continue; } String[] data = dataRow.split(" "); //分割資料行 if (!outProvince.contains(data[0])) { outProvince.add(data[0]); } synchronized (statistics) { //線程同步,給資料加鎖 List<Integer> provinceData = statistics.get(data[0]); //目前行的省份資料 List<Integer> destProvince; //用于處理流入 switch (data[1]) { case INCREMENT: //處理新增 if (data[2].equals(INFECTION_PATIENT)) { //新增感染 increaseInf(provinceData, Lib.parseData(data[3])); } else { //新增疑似 increaseSus(provinceData, Lib.parseData(data[3])); } break; case EXCLUDE: //處理排除疑似 excludeSus(provinceData, Lib.parseData(data[3])); break; case CURE: //處理治愈 cure(provinceData, Lib.parseData(data[2])); break; case DEAD: //處理死亡 dead(provinceData, Lib.parseData(data[2])); break; case INFECTION_PATIENT: //處理感染患者流入 destProvince = statistics.get(data[3]); infInflow(provinceData, destProvince, Lib.parseData(data[4])); break; case SUSPECTED_PATIENT: if (data[2].equals(INFLOW)) { //處理疑似患者流入 destProvince = statistics.get(data[3]); susInflow(provinceData, destProvince, Lib.parseData(data[4])); } else if (data[2].equals(DIAGNOSE)) { //處理确診 diagnose(provinceData, Lib.parseData(data[3])); } break; } } } } catch (Exception e) { e.printStackTrace(); } finally { if(reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } }); threadPool.shutdown(); while (!threadPool.isTerminated()); //等待所有線程執行完
-
優化結果
2020寒假作業(2/2) 可以看到程式執行時間從1142ms優化到了972ms,檔案數量越多的話優化效果越明顯。由于要兼顧線程同步,耗費的時間并沒有太大的改善,或許換成其他的線程加鎖方式能改善許多。2020寒假作業(2/2)
-
7、心路曆程與收獲
剛開始看到作業的時候人是懵的,畢竟有太多知識是第一次接觸到,比如PSP表格,單元測試等。後來去查資料,去認真的學習新的知識,我也對軟體工程和項目開發過程也有了更多的了解。按照作業要求分析需求,搭建好了程式基本結構,發現看似複雜的任務也簡單了起來。然後就是寫代碼,做測試,寫代碼,做測試。。。直到最終完成所有需求。開發中遇到了許多的bug,都在測試過程中很容易的解決掉了,也讓我感受到在程式設計中測試發現bug解決起來比程式設計完成後的測試發現bug解決起來容易得多。最後性能測試的過程中使用了專門的性能分析工具,也讓我更能看到代碼中需要優化的部分,優化起來也友善的多。
總的來說這次作業收獲還是很大的,學到了更多項目開發的知識,學到了單元測試和性能測試工具的使用,學到了GitHub和簡單git指令的使用。學無止境,希望下次作業能收獲更多。