# 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 + "秒.");
}
}
```