java 使用 poi 解析excel


背景:

web應用經常需要上傳文件,有時候需要解析出excel中的數據,如果excel的格式沒有問題,那就可以直接解析數據入庫。

工具選擇:

目前jxl和poi可以解析excel,jxl很早就停止維護了,只支持excel-2003也就是xls格式的文件;
 
poi可支持xls和xlsx格式的文件,經過考察,poi的功能強大很多,所以選擇這個工具解析excel。文件上傳在之前的一個專題有所提及。
 
需要如下jar包,jar包見附件,也可在官網下載。
注意:
1. 不支持單元格合並的情況,默認表格格式規范,格式規范指:
從第一行第一列開始,第一行為表頭,后面全都是數據,每一個列代表一個字段。沒有中途斷掉的行或者列,允許單元格為空。
 
2. 代碼初步運用了重構中的方法抽取(extract method)和方法對象抽取(extract method object)技術,代碼行數前后沒有太大的變化,但是結構明顯要清楚一些。
只不過初步使用,有些抽取不是特別直觀,寫文件的兩個方法沒有使用重構技術,此外在單元格類型匹配,也還有深挖的空間,希望讀者根據自己的需要,自行定制。
關於重構技術和解析excel的api,請參考《重構改善既有代碼的設計》以及poi的源代碼
 
  1 package com.test;
  2 import org.apache.poi.hssf.usermodel.HSSFDataFormat;
  3 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
  4 import org.apache.poi.ss.usermodel.*;
  5 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
  6 import java.io.*;
  7 import java.text.DecimalFormat;
  8 import java.text.SimpleDateFormat;
  9 import java.util.*;
 10 /**
 11  * Created by tm on 2016/4/11.
 12  *
 13  * @author tm
 14  *         使用POI解析和生成excel,暫不支持單元格合並情況,默認是標准格式的excel
 15  *         實測同等的數據量(3張sheet超過2w條),xlsx的速度要遠慢於xls的解析速度。
 16  *         同等數據量:
 17  *         xls的解析,如果只取表頭,不超過500毫秒;如果取整個數據,時間不超過2000毫秒;
 18  *         而xlsx的解析,如果只取表頭,需要2000毫秒左右,如果取整個數據,則在3000毫秒以上。
 19  *         以上耗時不同系統不同硬件有不同結果,僅作為相對比較結果。
 20  * 這個類提供三個功能:
 21  *         1.獲得表格的表頭所有列,這個功能可以酌情去掉,因為獲得表格的所有數據也就獲得了所有列.
 22  *         2.獲得表格的所有數據
 23  *         3.傳入數據寫出來
 24  */
 25 public class ExcelUtil {
 26     public static final String SUFFIX_XLS = "xls";
 27     public static final String SUFFIX_XLSX = "xlsx";
 28     public static final String NO_FILE_NULL = "NO FILE NULL";
 29     public static final String NO_FILE_EMPTY = "NO FILE EMPTY";
 30     /**
 31      * 獲得表格的列
 32      * @param fileName
 33      * @return
 34      * @throws RuntimeException
 35      */
 36     public static List<Map<String, Object>> getTableSheetCol(String fileName) throws RuntimeException{
 37         if(!valid(fileName))
 38             return null;
 39         else{
 40             if(validExcelFormat(fileName,SUFFIX_XLS)){
 41                 //是excel2003?
 42                 return getXlsSheetItem(fileName);
 43             }
 44             if(validExcelFormat(fileName, SUFFIX_XLSX)){
 45                 //是excel2007以后
 46                 return getXlsxSheetItem(fileName);
 47             }else{
 48                 print("nonsupport file format");
 49                 print("fileFormat : " + getFileNameSuffix(fileName));
 50                 throw new RuntimeException("nonsupport file format, please check input fileName again");
 51             }
 52         }
 53     }
 54     /**
 55      * getXlsSheetItem : 讀取xls格式文件的所有sheet表的所有列名集合。
 56      *
 57      * @param fileName 文件名
 58      *                  默認表格格式規范,列名全部為字符串。
 59      */
 60     private static List<Map<String, Object>> getXlsSheetItem(String fileName) {
 61         TableObject tableObject = new TableObject(fileName).invoke();
 62         HSSFWorkbook book = tableObject.getHssfBook();
 63         List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
 64         for (int i = 0; i < tableObject.getSheet_number(); i++) {
 65             Sheet sheet = book.getSheetAt(i);
 66             if (ifSheetNullOrEmpty(sheet)) continue;
 67             Row row = sheet.getRow(0);
 68             if (ifRowNullOrEmpty(row)) continue;
 69             result.add(packageColsWithSheetName(book, i, row));
 70         }
 71         return result;
 72     }
 73     /**
 74      * getXlsxSheetItem : 讀取xlsx格式文件的所有sheet表的所有列名集合。
 75      *
 76      * @param fileName 文件名
 77      *                  默認表格格式規范,列名全部為字符串。
 78      * 方法還可以進一步化簡,可參考 getExcelData 進一步整合。
 79      */
 80     private static List<Map<String, Object>> getXlsxSheetItem(String fileName) {
 81         TableObject tableObject = new TableObject(fileName).invoke();
 82         XSSFWorkbook book = tableObject.getXssfBook();
 83         List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
 84         for (int i = 0; i < tableObject.getSheet_number(); i++) {
 85             Sheet sheet = book.getSheetAt(i);
 86             if (ifSheetNullOrEmpty(sheet)) continue;
 87             Row row = sheet.getRow(0);
 88             if (ifRowNullOrEmpty(row)) continue;
 89             result.add(packageColsWithSheetName(book, i, row));
 90         }
 91         return result;
 92     }
 93     private static Map<String, Object> packageColsWithSheetName(Workbook book, int i, Row row) {
 94         Map<String, Object> map = new HashMap<String, Object>();
 95         String sheet_name = book.getSheetName(i);
 96         List<String> cols = getCols(row);
 97         map.put("sheet_name", sheet_name);
 98         map.put("cols", cols);
 99         return map;
100     }
101     private static List<String> getCols(Row row) {
102         List<String> cols = new ArrayList<String>();
103         System.out.println(row.getLastCellNum());
104         for (int j = 0; j < row.getLastCellNum(); j++) {
105             Object obj = row.getCell(j);
106             cols.add(obj == null ? "" : obj.toString());
107         }
108         return cols;
109     }
110     private static boolean ifRowNullOrEmpty(Row row) {
111         if (row == null || row.getLastCellNum() == 0 || row.getCell(0) == null) {
112             return true;
113         }
114         return false;
115     }
116     private static boolean ifSheetNullOrEmpty(Sheet sheet) {
117         if (sheet == null || sheet.getLastRowNum() == 0) {
118             return true;
119         }
120         return false;
121     }
122     /**
123      * 獲取表格的全部數據
124      * @param fileName 文件名
125      * @return
126      * @throws RuntimeException
127      */
128     public static Map<String, List<Map<String, Object>>> getTableSheetData(String fileName) throws RuntimeException{
129         if(!valid(fileName)) {
130             print("文件名校驗失敗");
131             return null;
132         }
133         else{
134             return getExcelData(fileName);
135         }
136     }
137     /**
138      * 根據文件后綴格式,確定調用哪種Workbook,此處運用了多態,具體的解析操作都用的是接口。
139      * @param fileName
140      * @return
141      */
142     private static Map<String, List<Map<String, Object>>> getExcelData(String fileName) {
143         TableObject tableObject = new TableObject(fileName).invoke();
144         Workbook book = null;
145         if(validExcelFormat(fileName,SUFFIX_XLS)){
146             //是excel2003?
147             book = tableObject.getHssfBook();
148         }
149         else if(validExcelFormat(fileName, SUFFIX_XLSX)){
150             //是excel2007以后
151             book = tableObject.getXssfBook();
152         }else{
153             print("nonsupport file format");
154             throw new RuntimeException("nonsupport file format, please check input fileName again");
155         }
156         Map<String, List<Map<String, Object>>> result = new HashMap<String, List<Map<String, Object>>>();
157         for (int i = 0; i < tableObject.getSheet_number(); i++) {
158             List<Map<String, Object>> sheet_data = new ArrayList<Map<String, Object>>();
159             Sheet sheet = book.getSheetAt(i);
160             if (ifSheetNullOrEmpty(sheet)) continue;
161             String sheet_name = book.getSheetName(i);
162             System.out.println(sheet.getLastRowNum());
163             for (int j = 1; j <= sheet.getLastRowNum(); j++) {
164                 Row row = sheet.getRow(j);
165                 if (ifRowNullOrEmpty(row)) continue;
166                 Map<String, Object> record = new HashMap<String, Object>();
167                 Row first = sheet.getRow(0);
168                 getRowData(row, record, first);
169                 sheet_data.add(record);
170             }
171             result.put(sheet_name, sheet_data);
172         }
173         return result;
174     }
175     
176     /**
177      * 此處有個點要注意,getLastCellNum,下標是從1開始,有多少列,這里就是這個值.而getLastRowNum,下標是從0開始,也就是21行的表格,這里獲得的值是20.用戶可自行驗證.
178      * @param row 該行記錄
179      * @param record 返回值
180      * @param first 表頭
181      */
182     private static void getRowData(Row row, Map<String, Object> record, Row first) {
183         for (int k = 0; k < row.getLastCellNum(); k++) {
184             String value;
185             if (row.getCell(k) == null) {
186                 value = "";
187             } else {
188                 value = parseDate(row.getCell(k));
189                 if (value.endsWith("00:00:00")) {
190                     value = value.substring(0, value.lastIndexOf("00:00:00"));
191                 }
192             }
193             record.put(first.getCell(k).toString(), value);
194         }
195     }
196     /**
197      * 判斷單元格格式,轉化日期格式,日期在poi里保存的是數字,所以這里要轉化一下.
198      * @param cell 單元格
199      * @return
200      */
201     private static String parseDate(Cell cell) {
202         String result = "";
203         switch (cell.getCellType()) {
204             case Cell.CELL_TYPE_NUMERIC:// 數字類型
205                 if (DateUtil.isCellDateFormatted(cell)) {// 處理日期格式、時間格式
206                     SimpleDateFormat sdf = null;
207                     if (cell.getCellStyle().getDataFormat() == HSSFDataFormat
208                             .getBuiltinFormat("yyyy/MM/dd")) {
209                         sdf = new SimpleDateFormat("yyyy/MM/dd");
210                     } else {// 日期
211                         sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
212                     }
213                     Date date = cell.getDateCellValue();
214                     result = sdf.format(date);
215                 } else if (cell.getCellStyle().getDataFormat() == 58) {
216                     // 處理自定義日期格式:m月d日(通過判斷單元格的格式id解決,id的值是58)
217                     SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
218                     double value = cell.getNumericCellValue();
219                     Date date = org.apache.poi.ss.usermodel.DateUtil
220                             .getJavaDate(value);
221                     result = sdf.format(date);
222                 } else {
223                     double value = cell.getNumericCellValue();
224                     CellStyle style = cell.getCellStyle();
225                     DecimalFormat format = new DecimalFormat();
226                     String temp = style.getDataFormatString();
227                     // 單元格設置成常規
228                     if (temp.equals("General")) {
229                         format.applyPattern("#");
230                     }
231                     result = format.format(value);
232                 }
233                 break;
234             case Cell.CELL_TYPE_STRING:// String類型
235                 result = cell.getRichStringCellValue().toString();
236                 break;
237             case Cell.CELL_TYPE_BLANK:
238                 result = "";
239                 break;
240             default:
241                 result = "";
242                 break;
243         }
244         return result;
245     }
246     /**
247      * ifXls:判斷是否為 xls 文件
248      *
249      * @param fileName 包含后綴的文件名
250      */
251     private static boolean validExcelFormat(String fileName,String type) {
252         if(getOS().contains("win")){
253             return valid(fileName) && getFileNameSuffix(fileName).equalsIgnoreCase(type);
254         }else if(getOS().contains("linux")){
255             return valid(fileName) && getFileNameSuffix(fileName).equals(type);
256         }else{
257             print("System OS is not windows or linux");
258             throw new RuntimeException("System OS is not windows or linux , if you use this class in another sys,please implement yourself");
259         }
260     }
261     private static String getOS() { return System.getProperty("os.name").toLowerCase(); }
262     private static void print(String msg) {
263         System.out.println(msg);
264     }
265     private static String getFileNameSuffix(String fileName) {
266         return fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length());
267     }
268     /**
269      * valid :判斷文件是否存在
270      *
271      * @param fileName fileName 文件名。
272      *                 區分為文件名為null,或者為空。
273      */
274     private static boolean valid(String fileName) {
275         return !ifNull(fileName) && !ifEmpty(fileName);
276     }
277     private static boolean ifEmpty(String fileName) {
278         if (fileName.equals("")) {
279             print(NO_FILE_EMPTY);
280             return true;
281         }
282         return false;
283     }
284     private static boolean ifNull(String fileName) {
285         if (fileName == null) {
286             print(NO_FILE_NULL);
287             return true;
288         }
289         return false;
290     }
291     /**
292      * 將excel文件寫出成txt格式
293      * 此處沒有指定輸出格式
294      * @param filePath 輸入文件路徑
295      * @param destFilePath 輸出文件路徑
296      * @throws Exception
297      */
298     public static void writeExcel2Txt(String filePath,String destFilePath) throws Exception {
299         BufferedWriter bufferedWriter = null;
300         try {
301             FileWriter fw = new FileWriter(destFilePath);
302             bufferedWriter = new BufferedWriter(fw);
303         } catch (IOException e) {
304             e.printStackTrace();
305             return;
306         }
307         Map<String, List<Map<String, Object>>> data = getTableSheetData(filePath);
308         System.out.println(data.toString());
309         if(data==null){
310             throw new Exception("解析數據失敗");
311         }
312         for (int i = 0; i < data.size(); i++) {
313             try {
314                 bufferedWriter.write(DataTransformUtil.mapGetKeyList(data).get(i));
315                 System.out.println(DataTransformUtil.mapGetKeyList(data).get(i));
316                 bufferedWriter.newLine();
317                 bufferedWriter.write("===========================================================");
318                 bufferedWriter.newLine();
319             } catch (IOException e) {
320                 e.printStackTrace();
321             }
322             System.out.println("==========================================");
323             List<Map<String, Object>> sheet = data.get(DataTransformUtil.mapGetKeyList(data).get(i));
324             for (Map map : sheet) {
325                 System.out.println(map.toString());
326                 bufferedWriter.write(map.toString());
327                 bufferedWriter.newLine();
328             }
329             System.out.println();
330             bufferedWriter.newLine();
331         }
332         bufferedWriter.flush();
333         bufferedWriter.close();
334     }
335     /***
336      * writeXlsxFile:將指定數據集生成為excel文件。
337      *
338      * @param data 一個包含record的List集合
339      * @param name 文件名【帶后綴】
340      * @param path 路徑名
341      *             只是簡單輸出,尚未設置樣式,單元格格式也未深究。
342      */
343     public static String writeXlsxFile(List<Map<String, Object>> data, String path, String name) {
344         // 創建Excel的工作書冊 Workbook,對應到一個excel文檔
345         Workbook book = null;
346         if(validExcelFormat(name,SUFFIX_XLS)){
347             book = new HSSFWorkbook();
348         }else if(validExcelFormat(name,SUFFIX_XLSX)){
349             book = new XSSFWorkbook();
350         }else {
351             print("nonsupport file format");
352             throw new RuntimeException("nonsupport file format, please check input fileName again");
353         }
354         // 創建Excel的工作sheet,對應到一個excel文檔的tab
355         Sheet sheet = book.createSheet("sheet1");
356         sheet.setColumnWidth(0, 4000);
357         sheet.setColumnWidth(1, 3500);
358         // 創建Excel的sheet的一行
359         int c = data.get(0).size();
360         List cols = DataTransformUtil.mapGetKeyList(data.get(0));
361         Row head = sheet.createRow(0);
362         for (int j = 0; j < cols.size(); j++) {
363             Cell cell = head.createCell(j, 1);
364             cell.setCellValue(cols.get(j).toString());
365         }
366         for (int i = 0; i < data.size(); i++) {
367             Row row = sheet.createRow(i + 1);
368             for (int j = 0; j < c; j++) {
369                 Cell cell = row.createCell(j, 1);
370                 cell.setCellValue(data.get(i).get(cols.get(j).toString()).toString());
371             }
372         }
373         FileOutputStream os = null;
374         try {
375             os = new FileOutputStream(new File(new File(path), name));
376             book.write(os);
377             os.close();
378         } catch (Exception e) {
379             e.printStackTrace();
380         }
381         return name ;
382     }
383     public static void main(String[] args) throws Exception {
384 //        String str="[{'name':'zhang3','age':33,'gender':'f'}," +
385 //                "{'name':'li4','age':44,'gender':'m'}," +
386 //                "{'name':'wang5','age':55,'gender':'m'}," +
387 //                "{'name':'wang5','age':55,'gender':'m'}," +
388 //                "{'name':'wang5','age':55,'gender':'m'}]";
389 //        List<Map<String,Object>> list=DataTransformUtil.nestJsonArrayString2List(str);
390 //        System.out.println(list.toString());
391 //        writeXlsxFile(list,"F:\\","test");
392         long start = System.currentTimeMillis();
393         System.out.println("start :" + System.currentTimeMillis());
394        // writeExcel2Txt("F:\\152657884.xls","F:\\123.txt");
395         //System.out.println(getTableSheetCol("F:\\需要本地開發功能列表.xlsx"));
396         System.out.println(getTableSheetData("F:\\需要本地開發功能列表.xlsx"));
397     }
398     /**
399      * extract method object
400      */
401     private static class TableObject {
402         private String fileName;
403         private XSSFWorkbook xssfBook;
404         private HSSFWorkbook hssfBook;
405         private int sheet_number;
406         public TableObject(String fileName) {
407             this.fileName = fileName;
408         }
409         public XSSFWorkbook getXssfBook() {
410             return xssfBook;
411         }
412         public HSSFWorkbook getHssfBook() {
413             return hssfBook;
414         }
415         public int getSheet_number() {
416             return sheet_number;
417         }
418         public TableObject invoke() {
419             xssfBook = null;
420             hssfBook = null;
421             sheet_number = 0;
422             try {
423                 FileInputStream is = new FileInputStream(new File(fileName));
424                 if(validExcelFormat(fileName,SUFFIX_XLS)){
425                     hssfBook = new HSSFWorkbook(is);
426                     sheet_number = hssfBook.getNumberOfSheets();
427                 }else if(validExcelFormat(fileName,SUFFIX_XLSX)){
428                     xssfBook = new XSSFWorkbook(is);
429                     sheet_number = xssfBook.getNumberOfSheets();
430                 }
431             } catch (IOException e) {
432                 e.printStackTrace();
433             }
434             return this;
435         }
436     }
437 }

發現有個WorkBookFactory的api方法,可以直接拿到對應的HSSFWorkbook或XSSFWorkbook

所以說,研究清楚api才是正道啊……

大牛博客傳送門:http://blog.csdn.net/lovesomnus/article/details/23843549 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM