---
# System prepended metadata

title: COVID-19每日「確診人數」與「死亡人數」
tags: [java, covid-19]

---

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


