小蛙很久很久以前發過一篇 Java + Excel = JXL,主要講解怎麼用 Java 處理 Excel,當時使用的是 jxl 這個套件,不過這個套件有一個很大的問題,就是只能處理 xls 的檔案,xlsx 沒有辦法處理,這篇要介紹的 Apache POI 則可以用來處理 xls 跟 xlsx。
使用套件
小蛙這邊說明自己下載 jar 的方式,首先到這邊下載 Apache POI,下載回來解壓縮之後,會得到一堆 jar,最簡單最簡單就是先把下面這些 jar 通通引入
通通引入之後,小蛙放上參考網路上資料後,改成自己適用的功能片段,主要作法是先讀取 Excel 裡的所有資料丟到 List<List<String>> 裡面,再透過操作 List<List<String>> 的資料達到目的,當然也可以改成最直觀的方式,直接讀取特定欄位直接做處理。(不一定要用這種全部讀出來再去處理的方式,也可以直接一格一格讀出來處理),下面只是列出一些 function,把它改成自己需要的方式就可以了。
操作流程大概是這樣
讀取 xls / xlsx 檔案
第一步就是讀取 excel 檔案,這邊多做了一件事情就是區分出 xls 跟 xlsx,用一個 if 判斷是分別 new 出 HSSFWorkbook / XSSFWorkbook 物件。
// 從路徑中讀取 Excel default Workbook getWorkbook(String path) throws FileNotFoundException, IOException { Workbook wb = null; if(path == null) return null; String extString = path.substring(path.lastIndexOf(".")); InputStream is = new FileInputStream(path); if(XLS.equals(extString)){ wb = new HSSFWorkbook(is); }else if(XLSX.equals(extString)){ wb = new XSSFWorkbook(is); } return wb; }
讀取 sheet
從上面取得的 workbook 中,讀取特定頁籤,這個 sheetNo 從 0 開始計算,也就是說第一個頁籤的話要傳入 0,第二個頁籤傳入 1,以此類推。
// 讀取要用的 Sheet default Sheet getSheet(Workbook workbook, int sheetNo){ return workbook.getSheetAt(sheetNo); }
遍尋 rows(橫的)遍尋 columns(直的)
這邊跟 jxl 比較不同的地方在於,必須先把 row 取出來,然後取得最後一個 row 跟最後一個 column,要注意的是取得 colnum 的時候,要先判斷取出的 row 是不是 null,不然會噴錯喔!(小蛙這邊懶得改了,自己要記得加上判斷喔),以及 index 都是從 0 開始計算,然後每一格儲存格取出的資料都放在 List<List<String>>。中間 readCell 部份內容請往下看。
// 把所有欄位讀出成 List<List<String>> default List<List<String>> readFields(Workbook workbook, int sheetNo, int firstRow, int firstCol) throws Exception { Sheet sheet = workbook.getSheetAt(sheetNo); Row row = sheet.getRow(0); int rownum = sheet.getPhysicalNumberOfRows(); int colnum = row.getPhysicalNumberOfCells(); List<List<String>> list = new ArrayList<>(); List<String> _inner; for(int i = firstRow; i < rownum; i++){ row = sheet.getRow(i); _inner = new ArrayList<>(); if(row != null){ for(int j = firstCol; j < colnum; j++){ _inner.add(readCell(row.getCell(j))); } list.add(_inner); }else{ break; } } return list; }
轉換型態
跟 jxl 不一樣的部份是,jxl 可以直接取得字串型態的資料,但是 poi 必須根據不同的形態來呼叫不同的方法取得內容,下面是參考網路上看到的程式碼做的修改,原始來源不太記得了,大致上符合小蛙的需求,可以直接複製回去,再依自己的需求修改。
// 小蛙自己要吐出的格式 default String readCell(Cell cell){ return (String) getCellFormatValue(cell); } default Object getCellFormatValue(Cell cell){ Object cellValue = null; if(cell!=null){ switch(cell.getCellType()){ case NUMERIC: cellValue = df.format(cell.getNumericCellValue()); break; case FORMULA: if(DateUtil.isCellDateFormatted(cell)){ cellValue = cell.getDateCellValue(); }else{ cellValue = String.valueOf(cell.getNumericCellValue()); } break; case STRING: cellValue = cell.getRichStringCellValue().getString(); break; default: cellValue = ""; } }else{ cellValue = ""; } return cellValue; }
打完收工,把這些 code 改成自己需求之後組合起來應該就可以順利動囉!