# COVID-19每日「確診人數」與「死亡人數」 ###### tags: `java` `covid-19` ## 目錄 1. [需求定義](#需求定義) - [確認COVID-19資料來源](#確認COVID-19資料來源) - [確認規格](#確認規格) 2. [程式開發](#程式開發) - [完整程式碼](#完整程式碼) 3. [測試](#測試) 4. [發佈](#發佈) ## 需求定義 ### 確認COVID-19資料來源 來自美國「約翰·霍普金斯大學(Johns Hopkins University)」分校「惠廷工程學院(Whiting School of Engineering)」的每日更新的新冠肺炎COVID-19全球疫情資訊,檔案為:CSV(Comma-Separated Values)格式。 > Whiting School of Engineering wiki: https://en.wikipedia.org/wiki/Whiting_School_of_Engineering > > CSV wiki: https://en.wikipedia.org/wiki/Comma-separated_values ##### GitHub位址 ``` https://github.com/CSSEGISandData/COVID-19/tree/master/csse_covid_19_data/csse_covid_19_daily_reports ``` ##### 原始擋下載位置 ``` https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_daily_reports/{資料日期}.csv ``` 資料日期格式為:月-日-西元年,且日、月只有個位數需要補零,例如: ``` 08-23-2021 12-24-2020 ``` #### 規格確認 封裝成套件:covid19 類別名稱:DailyReport 1. 建立類別時指定要取得的資訊日期。 2. 下載遠端COVID-19 CSV檔案。 3. 解析CSV檔案取取出需要的資訊。 4. 將解析後的資訊輸出到標準輸出。 5. main()方法建立DailyReport物件,並取得30天內全球確診人數和全球死亡人數。 ## 程式開發 ##### 使用到的Java第三方套件 OpenCSV: https://sourceforge.net/projects/opencsv/ Apache common-lang3: https://mvnrepository.com/artifact/org.apache.commons/commons-lang3/3.12.0 > **備註** > > Apache common-lang為OpenCSV的依賴套件 > **提示** > > 將下載的jar檔複製到專案中的lib目錄下即可。 > **注意** > > 套件名稱不可以有符號,例如:「-」等等。 #### 完整程式碼 - 單執行緒版 ##### covid19/DailyReport.java ``` package covid19; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.HashMap; import java.util.Map; import com.opencsv.CSVReader; import com.opencsv.exceptions.CsvValidationException; public class DialyReport { private String dayStr = ""; public DialyReport(String dayStr) { this.dayStr = dayStr; } // 要下載的csv網址 final String URL_TEMPLATE = "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_daily_reports/%s.csv"; final String KEY_CONFIRMED = "confirmed"; // 用在Map的確診人數的key final String KEY_DEATH = "death"; // 用在Map的死亡人數的key final String HTML_FILENAME = "index.html";// 網頁檔名 public static String dayStr(int offset) { final Calendar cal = Calendar.getInstance(); // 取得今天日期時間 cal.add(Calendar.DATE, offset); // 將Calendar物件轉成日期字串並回傳 // 1. new SimpleDateFormat("MM-dd-yyyy") 建立SimpleDataFormat並設定日期格式 // 2. format() 將DateTime物件傳給SimpleDateFormat // 3. cal.getTime() 將Calendar物件轉成DateTime物件 // 4. return 將轉換完成的自傳回傳 return new SimpleDateFormat("MM-dd-yyyy").format(cal.getTime()); } protected CSVReader downloadCsv(String dayStr) throws CsvValidationException, IOException { // 產生格式化後的網址 String strUrl = String.format(URL_TEMPLATE, dayStr); // 建立URL物件 URL url = new URL(strUrl); // 透過HttpURLConnection開始連線 HttpURLConnection connection = (HttpURLConnection)url.openConnection(); // 判斷連線使否成功(HTTP回應碼為200) if (connection.getResponseCode() == 200) { // 取得連線的stream並建立stream reader InputStreamReader isr = new InputStreamReader(connection.getInputStream()); // 將stream reader傳給CSVReader CSVReader csvReader = new CSVReader(isr); csvReader.readNext(); // 讀出標題 return csvReader; } return null; // 連線失敗,回傳空物件 } protected Map<String, Integer> parseCsv(CSVReader csvReader) throws IOException, CsvValidationException { int confirmed = 0; // 全球確診人數總和 int death = 0; // 全球死亡人數 String[] line = null; // 遍歷全球新冠肺炎CSV資料 while ((line = csvReader.readNext()) != null) { // System.out.println(Arrays.toString(line) + line.length); confirmed += Integer.parseInt(line[7]); // 將確診人數加起來 death += Integer.parseInt(line[8]); // 將死亡人數加起來 } // 關閉CSVReader,檔案相關操作,都需要做close動作 csvReader.close(); Map<String, Integer> result = new HashMap<>(); result.put(KEY_CONFIRMED, confirmed); // 存入確診人數資料 result.put(KEY_DEATH, death); // 存入死亡人數資料 return result; } // 產生網頁 public void gen() { try { CSVReader csvReader = downloadCsv(dayStr); if(csvReader == null) { System.out.printf("%s - 無資料.\n", dayStr); return; // 結束這個方法 } // 解析CSV資料並算出全球確診人數和死亡人數 Map<String, Integer> result = parseCsv(csvReader); // 輸出資訊 System.out.printf("%s - 確診人數: %d, 死亡人數: %d\n", dayStr, result.get(KEY_CONFIRMED), result.get(KEY_DEATH)); } catch(IOException e) { System.out.println("錯誤: " + e.getMessage()); } catch(CsvValidationException e) { System.out.println("錯誤: " + e.getMessage()); } } } ``` ##### app.java ``` import java.util.ArrayList; import covid19.DialyReport; public class App { public static void main(String[] args) throws Exception { long startTime = System.currentTimeMillis(); ArrayList<Thread> jobs = new ArrayList<>(); // 要計算的程式碼 for(int i = 0 ; i < 30 ; i++) { new DialyReport(DialyReport.dayStr(-i)).gen(); } long stopTime = System.currentTimeMillis(); System.out.println("總共費時:" + (stopTime - startTime) / 1000 + "秒."); } } ``` #### 完整程式碼 - 多執行緒版 ##### covid19/DailyReport.java ``` package covid19; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.HashMap; import java.util.Map; import com.opencsv.CSVReader; import com.opencsv.exceptions.CsvValidationException; public class DialyReport extends Thread { private String dayStr = ""; public DialyReport(String dayStr) { this.dayStr = dayStr; } // 要下載的csv網址 final String URL_TEMPLATE = "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_daily_reports/%s.csv"; final String KEY_CONFIRMED = "confirmed"; // 用在Map的確診人數的key final String KEY_DEATH = "death"; // 用在Map的死亡人數的key final String HTML_FILENAME = "index.html";// 網頁檔名 public static String dayStr(int offset) { final Calendar cal = Calendar.getInstance(); // 取得今天日期時間 cal.add(Calendar.DATE, offset); // 將Calendar物件轉成日期字串並回傳 // 1. new SimpleDateFormat("MM-dd-yyyy") 建立SimpleDataFormat並設定日期格式 // 2. format() 將DateTime物件傳給SimpleDateFormat // 3. cal.getTime() 將Calendar物件轉成DateTime物件 // 4. return 將轉換完成的自傳回傳 return new SimpleDateFormat("MM-dd-yyyy").format(cal.getTime()); } protected CSVReader downloadCsv(String dayStr) throws CsvValidationException, IOException { // 產生格式化後的網址 String strUrl = String.format(URL_TEMPLATE, dayStr); // 建立URL物件 URL url = new URL(strUrl); // 透過HttpURLConnection開始連線 HttpURLConnection connection = (HttpURLConnection)url.openConnection(); // 判斷連線使否成功(HTTP回應碼為200) if (connection.getResponseCode() == 200) { // 取得連線的stream並建立stream reader InputStreamReader isr = new InputStreamReader(connection.getInputStream()); // 將stream reader傳給CSVReader CSVReader csvReader = new CSVReader(isr); csvReader.readNext(); // 讀出標題 return csvReader; } return null; // 連線失敗,回傳空物件 } protected Map<String, Integer> parseCsv(CSVReader csvReader) throws IOException, CsvValidationException { int confirmed = 0; // 全球確診人數總和 int death = 0; // 全球死亡人數 String[] line = null; // 遍歷全球新冠肺炎CSV資料 while ((line = csvReader.readNext()) != null) { // System.out.println(Arrays.toString(line) + line.length); confirmed += Integer.parseInt(line[7]); // 將確診人數加起來 death += Integer.parseInt(line[8]); // 將死亡人數加起來 } // 關閉CSVReader,檔案相關操作,都需要做close動作 csvReader.close(); Map<String, Integer> result = new HashMap<>(); result.put(KEY_CONFIRMED, confirmed); // 存入確診人數資料 result.put(KEY_DEATH, death); // 存入死亡人數資料 return result; } // 產生網頁 public void gen() { try { CSVReader csvReader = downloadCsv(dayStr); if(csvReader == null) { System.out.printf("%s - 無資料.\n", dayStr); return; // 結束這個方法 } // 解析CSV資料並算出全球確診人數和死亡人數 Map<String, Integer> result = parseCsv(csvReader); // 輸出資訊 System.out.printf("%s - 確診人數: %d, 死亡人數: %d\n", dayStr, result.get(KEY_CONFIRMED), result.get(KEY_DEATH)); } catch(IOException e) { System.out.println("錯誤: " + e.getMessage()); } catch(CsvValidationException e) { System.out.println("錯誤: " + e.getMessage()); } } public void run() { gen(); } } ``` 說明: 1. 繼承自Thread類別。 2. 複寫run()方法,並將要多緒執行的程式碼寫在run()方法內。 ##### app.java ``` import java.util.ArrayList; import covid19.DialyReport; public class App { public static void main(String[] args) throws Exception { long startTime = System.currentTimeMillis(); ArrayList<Thread> jobs = new ArrayList<>(); // 要計算的程式碼 for(int i = 0 ; i < 30 ; i++) { Thread t = new DialyReport(DialyReport.dayStr(-i)); t.start(); jobs.add(t); } // 等待全部執行緒完成再往下 for(Thread t : jobs) { t.join(); } long stopTime = System.currentTimeMillis(); System.out.println("總共費時:" + (stopTime - startTime) / 1000 + "秒."); } } ```