Java 使用 POI 操作 Excel
Apache POI 基本介紹
Apache POI 是 Apache 軟件基金會(huì)提供的 100% 開源庫(kù)。支持 Excel 庫(kù)的所有基本功能。
圖片來源:易百教程
基本概念
在 POI 中,Workbook代表著一個(gè) Excel 文件(工作簿),Sheet代表著 Workbook 中的一個(gè)表格,Row 代表 Sheet 中的一行,而 Cell 代表著一個(gè)單元格。 HSSFWorkbook對(duì)應(yīng)的就是一個(gè) .xls 文件,兼容 Office97-2003 版本。 XSSFWorkbook對(duì)應(yīng)的是一個(gè) .xlsx 文件,兼容 Office2007 及以上版本。 在 HSSFWorkbook 中,Sheet接口 的實(shí)現(xiàn)類為 HSSFSheet,Row接口 的實(shí)現(xiàn)類為HSSFRow,Cell 接口的實(shí)現(xiàn)類為 HSSFCell。 XSSFWorkbook 中實(shí)現(xiàn)類的命名方式類似,在 Sheet、Row、Cell 前加 XSSF 前綴即可。
引入依賴
<!-- 基本依賴,僅操作 xls 格式只需引入此依賴 --> <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>3.14</version> </dependency> <!-- 使用 xlsx 格式需要額外引入此依賴 --> <dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>3.14</version> </dependency> 復(fù)制代碼使用 POI
使用 POI 的目的就是為了在 Java 中解析/操作 Excel 表格,實(shí)現(xiàn) Excel 的導(dǎo)入/導(dǎo)出的功能,接下來我們依次來看它們的實(shí)現(xiàn)代碼及注意事項(xiàng)。
導(dǎo)出
導(dǎo)出操作即使用 Java 寫出數(shù)據(jù)到 Excel 中,常見場(chǎng)景是將頁(yè)面上的數(shù)據(jù)(可能是經(jīng)過條件查詢的)導(dǎo)出,這些數(shù)據(jù)可能是財(cái)務(wù)數(shù)據(jù),也可能是商品數(shù)據(jù),生成 Excel 后返回給用戶下載文件。 該操作主要涉及 Excel 的創(chuàng)建及使用流輸出的操作,在 Excel 創(chuàng)建過程中,可能還涉及到單元格樣式的操作。
創(chuàng)建并導(dǎo)出基本數(shù)據(jù)
進(jìn)行導(dǎo)出操作的第一步是創(chuàng)建 Excel 文件,我們寫一個(gè)方法,參數(shù)是需要寫入 Excel 表格的數(shù)據(jù)和生成 Excel 方式(HSSF,XSSF),返回一個(gè) Workbook 接口對(duì)象。 在方法內(nèi)部我們采用反射來創(chuàng)建 Workbook 的實(shí)例對(duì)象。
代碼
探索階段,我們先將數(shù)據(jù)類型限定為 List,并把列數(shù)限定為某個(gè)數(shù)字,生成一個(gè)表格。 代碼如下:
import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook;import java.util.List; /*** Excel 工廠類,負(fù)責(zé) Workbook 的生成和解析** @author calmer* @since 2018/12/5 11:19*/ public class ExcelFactory {/*** 構(gòu)造 Workbook 對(duì)象,具體實(shí)例化哪種對(duì)象由 type 參數(shù)指定* @param data 要導(dǎo)出的數(shù)據(jù)* @param type Excel 生成方式* @return 對(duì)應(yīng) type 的工作簿實(shí)例對(duì)象* @throws Exception 反射生成對(duì)象時(shí)出現(xiàn)的異常* <li>InstantiationException</li>* <li>IllegalAccessException</li>* <li>InstantiationException</li>*/public static Workbook createExcel(List data,String type) throws Exception{//根據(jù) type 參數(shù)生成工作簿實(shí)例對(duì)象Workbook workbook = (Workbook) Class.forName(type).newInstance();//這里還可以指定 sheet 的名字//Sheet sheet = workbook.createSheet("sheetName");Sheet sheet = workbook.createSheet();// 限定列數(shù)int cols = 10;int rows = data.size() / cols;int index = 0;for (int rowNum = 0; rowNum < rows; rowNum++) {Row row = sheet.createRow(rowNum);for (int colNum = 0; colNum < cols; colNum++) {Cell cell = row.createCell(colNum);cell.setCellValue(data.get(index++).toString());}}return workbook;} } 復(fù)制代碼調(diào)用時(shí),我們生成好數(shù)據(jù)并構(gòu)造好 Workbook 對(duì)象,再調(diào)用 Workbook 的 write(OutputStream stream) 方法生成 Excel 文件。
List<String> strings = new ArrayList<>(); for (int i = 0; i < 1000; i++) {strings.add(Integer.toString(i+1)); } FileOutputStream out = new FileOutputStream("F:\\testXSSF.xlsx"); ExcelFactory.createExcel(strings,"org.apache.poi.xssf.usermodel.XSSFWorkbook").write(out); out.close(); 復(fù)制代碼生成結(jié)果:
問題
以上代碼已經(jīng)完成簡(jiǎn)單的 Excel 文件生成操作,但其中還有幾點(diǎn)問題沒有解決
- 實(shí)際場(chǎng)景下,Excel 表格中可能并不會(huì)存 Integer、String 這種基本數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù),更多的可能是對(duì)象數(shù)據(jù)(JSON、List),需要有表頭,并將對(duì)象對(duì)應(yīng)的屬性一行行的顯示出來(參考數(shù)據(jù)庫(kù)查詢語(yǔ)句執(zhí)行的結(jié)果)。并且表頭的樣式一定是要控制的。
- 我們并沒有對(duì)方法中 type 屬性進(jìn)行限制,即外部可以傳來任何類似“a”、“b”這樣的無效值,屆時(shí)程序會(huì)拋出異常,可以使用靜態(tài)常量或枚舉類來限定,這樣可以增強(qiáng)代碼可讀性和健壯性。這里我并不想用靜態(tài)常量或枚舉類,打算使用注解的方式來控制參數(shù)的有效性。
完善
我們已經(jīng)明確了兩個(gè)問題:
我們先來解決第二個(gè)問題,即參數(shù)的問題。
使用注解限定參數(shù)
首先創(chuàng)建一個(gè)注解類
import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy;/**** @author calmer* @since 2018/12/5 12:27*/ (RetentionPolicy.SOURCE) public ExcelType {String HSSF = "org.apache.poi.hssf.usermodel.HSSFWorkbook";String XSSF = "org.apache.poi.xssf.usermodel.XSSFWorkbook"; }復(fù)制代碼在方法參數(shù)上加上注解
public static Workbook createExcel(List data, @ExcelType String type) throws Exception {//內(nèi)容省略} 復(fù)制代碼調(diào)用時(shí)
ExcelFactory.createExcel(list,ExcelType.HSSF).write(out); 復(fù)制代碼關(guān)于使用注解來限定參數(shù)的取值范圍這種方式,我也是偶然看到過,可是這種方式在我這里編譯器并不會(huì)給任何提示,我對(duì)注解了解不夠,以后有機(jī)會(huì)要再好好研究一下。
解決實(shí)際數(shù)據(jù)問題
在實(shí)際應(yīng)用中,很常見的情況是我們有很多實(shí)體類,比如 Person,Product,Order 等,借助反射,我們可以獲取任意實(shí)體類的屬性列表、getter 方法,所以目前,我打算利用反射,來處理多個(gè)對(duì)象的 Excel 導(dǎo)出。 首先我們創(chuàng)建一個(gè)方法,用來獲取某個(gè)對(duì)象的屬性列表(暫時(shí)不考慮要獲取父類屬性的情況)。
/*** 獲取對(duì)象的屬性名數(shù)組* @param clazz Class 對(duì)象,用于獲取該類的信息* @return 該類的所有屬性名數(shù)組*/ private static String[] getFieldsName(Class clazz){Field[] fields = clazz.getDeclaredFields();String[] fieldNames = new String[fields.length];for (int i = 0; i < fields.length; i++) {fieldNames[i] = fields[i].getName();}return fieldNames; } 復(fù)制代碼然后我們完善 createExcel() 方法
public static Workbook createExcel(List data, @ExcelType String type) throws Exception {if(data == null || data.size() == 0){throw new Exception("數(shù)據(jù)不能為空");}//根據(jù)類型生成工作簿Workbook workbook = (Workbook) Class.forName(type).newInstance();//新建表格Sheet sheet = workbook.createSheet();//生成表頭Row thead = sheet.createRow(0);String[] fieldsName = getFieldsName(data.get(0).getClass());for (int i = 0; i < fieldsName.length; i++) {Cell cell = thead.createCell(i);cell.setCellValue(fieldsName[i]);}//保存所有屬性的getter方法名Method[] methods = new Method[fieldsName.length];for (int i = 0; i < data.size(); i++) {Row row = sheet.createRow(i+1);Object obj = data.get(i);for (int j = 0; j < fieldsName.length; j++) {//加載第一行數(shù)據(jù)時(shí),初始化所有屬性的getter方法if(i == 0){String fieldName = fieldsName[j];//處理布爾值命名 "isXxx" -> "setXxx"if (fieldName.contains("is")) {fieldName = fieldName.split("is")[1];}methods[j] = obj.getClass().getMethod("get" +fieldName.substring(0,1).toUpperCase() +fieldName.substring(1));}Cell cell = row.createCell(j);Object value = methods[j].invoke(obj);//注意判斷 value 值是否為空if(value == null){value = "無";}cell.setCellValue(value.toString());}}return workbook; } 復(fù)制代碼測(cè)試
以上代碼基本滿足一開始的需求,即以類的屬性名為表頭并生成表格。接下來我們生成一定量的數(shù)據(jù),并測(cè)試導(dǎo)出效果。 實(shí)體類代碼
/**** @author calmer* @since 2018/12/5 14:50*/ public class Person {private Integer id;private String name;private Integer age;private String hobby;private String job;private String address;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getHobby() {return hobby;}public void setHobby(String hobby) {this.hobby = hobby;}public String getJob() {return job;}public void setJob(String job) {this.job = job;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;} } 復(fù)制代碼測(cè)試類代碼
List<Person> list = new ArrayList<>(); for (int i = 0; i < 60000; i++) {int num = i + 1;Person person = new Person();person.setId(num);person.setName("張三-"+(num));person.setAddress("花園路"+num+"號(hào)"+(int)Math.ceil(Math.random()*10)+"號(hào)樓");person.setAge(i+18);person.setHobby("洗臉?biāo)⒀来駾OTA");person.setJob("程序員");list.add(person); } FileOutputStream out = new FileOutputStream("F:\\testXSSF.xlsx"); ExcelFactory.createExcel(list,ExcelType.XSSF).write(out); out.close(); 復(fù)制代碼生成的結(jié)果如下
其他
這里測(cè)試的時(shí)候我使用6W的數(shù)據(jù),所以程序進(jìn)行的比較慢,用時(shí)如下:
像這種大數(shù)據(jù)量的導(dǎo)出,我們可以使用 SXSSF 的方式,網(wǎng)上也有很多例子,官網(wǎng)的對(duì)比。使用 SXSSF 方式導(dǎo)出用時(shí)如下:
可以看到時(shí)間縮短了很多。接下來我們單獨(dú)來了解一下如何控制表格的樣式。
樣式
通常,我們需要控制的樣式有兩個(gè)部分,一個(gè)是表頭部分的樣式,另一個(gè)是普通單元格的樣式。這次我們就僅創(chuàng)建兩個(gè)方法演示樣式的設(shè)置方式。 在 POI 中,控制單元格樣式的對(duì)象是 CellStyle 接口,可以通過 Workbook 的createStyle 方法獲得實(shí)例對(duì)象,這里我們寫一個(gè)方法設(shè)置表頭的樣式。
private static CellStyle getTheadStyle(Workbook workbook){CellStyle style = workbook.createCellStyle();//設(shè)置填充色style.setFillForegroundColor(IndexedColors.LIGHT_BLUE.index);style.setFillPattern(CellStyle.SOLID_FOREGROUND);//設(shè)置對(duì)齊方式style.setAlignment(CellStyle.ALIGN_CENTER);//字體樣式Font font = workbook.createFont();//設(shè)置字體名稱font.setFontName("華文隸書");//斜體font.setItalic(true);//字體顏色font.setColor(IndexedColors.YELLOW.index);//字體大小font.setFontHeightInPoints((short)12);//不要忘記這句style.setFont(font);return style; } 復(fù)制代碼調(diào)用
Row thead = sheet.createRow(0); //設(shè)置行高 thead.setHeight((short) 500); //僅使用 setRowStyle 方法會(huì)對(duì)除有值的表頭設(shè)置樣式 thead.setRowStyle(style); String[] fieldsName = getFieldsName(data.get(0)); for (int i = 0; i < fieldsName.length; i++) {Cell cell = thead.createCell(i);cell.setCellValue(fieldsName[i]);//在這里循環(huán)為每個(gè)有值的表頭設(shè)置樣式。//結(jié)合上面的 setRowStyle 會(huì)將表頭行全部設(shè)置樣式cell.setCellStyle(style); } 復(fù)制代碼接下來我們寫獲取普通單元格樣式的方法
private static CellStyle getCommonStyle(Workbook workbook){CellStyle style = workbook.createCellStyle();//設(shè)置填充色style.setFillForegroundColor(IndexedColors.GREEN.index);style.setFillPattern(CellStyle.SOLID_FOREGROUND);//設(shè)置居中對(duì)齊style.setAlignment(CellStyle.ALIGN_CENTER);Font font = workbook.createFont();font.setFontName("華文彩云");//不要忘記這句style.setFont(font);return style; } 復(fù)制代碼完整調(diào)用
public static Workbook createExcel(List data, @ExcelType String type) throws Exception {if(data == null || data.size() == 0){throw new Exception("數(shù)據(jù)不能為空");}//根據(jù)類型生成工作簿Workbook workbook = (Workbook) Class.forName(type).newInstance();//生成樣式CellStyle style = getTheadStyle(workbook);//新建表格Sheet sheet = workbook.createSheet();//生成表頭Row thead = sheet.createRow(0);//設(shè)置行高thead.setHeight((short) 500);//僅使用 setRowStyle 方法會(huì)對(duì)除有值的表頭設(shè)置樣式thead.setRowStyle(style);String[] fieldsName = getFieldsName(data.get(0));for (int i = 0; i < fieldsName.length; i++) {Cell cell = thead.createCell(i);cell.setCellValue(fieldsName[i]);//在這里循環(huán)為每個(gè)有值的表頭設(shè)置樣式。//結(jié)合上面的 setRowStyle 會(huì)將表頭行全部設(shè)置樣式cell.setCellStyle(style);}//保存所有屬性的getter方法名Method[] methods = new Method[fieldsName.length];//獲取普通單元格樣式style = getCommonStyle(workbook);for (int i = 0; i < data.size(); i++) {Row row = sheet.createRow(i+1);Object obj = data.get(i);for (int j = 0; j < fieldsName.length; j++) {//加載第一行數(shù)據(jù)時(shí),初始化所有屬性的getter方法if(i == 0){String fieldName = fieldsName[j];methods[j] = obj.getClass().getMethod("get" +fieldName.substring(0,1).toUpperCase() +fieldName.substring(1));}Cell cell = row.createCell(j);Object value = methods[j].invoke(obj);//注意判斷 value 值是否為空if(value == null){value = "無";}cell.setCellValue(value.toString());//設(shè)置單元格樣式cell.setCellStyle(style);}}return workbook; } 復(fù)制代碼生成結(jié)果如下(忽視顏色搭配與美觀程度)
注意
這里我運(yùn)行的出了一個(gè)問題,在此記錄。 注意上面代碼的第 28 行和第 48 行,這里我們?cè)?for 循環(huán)外面獲取 Style 對(duì)象,在 for 循環(huán)中循環(huán)設(shè)置單元格樣式的時(shí)候,始終使用的是__同一個(gè)__ Style。而最開始我測(cè)試的時(shí)候,并不是這樣寫,而是像下面這樣:
for (int i = 0; i < data.size(); i++) {Row row = sheet.createRow(i+1);Object obj = data.get(i);for (int j = 0; j < fieldsName.length; j++) {//加載第一行數(shù)據(jù)時(shí),初始化所有屬性的getter方法if(i == 0){String fieldName = fieldsName[j];methods[j] = obj.getClass().getMethod("get" +fieldName.substring(0,1).toUpperCase() +fieldName.substring(1));}Cell cell = row.createCell(j);Object value = methods[j].invoke(obj);//注意判斷 value 值是否為空if(value == null){value = "無";}cell.setCellValue(value.toString());//設(shè)置單元格樣式cell.setCellStyle(getCommonStyle(workbook));} } 復(fù)制代碼注意 20 行,在 getCommonStyle 方法中,我們每次調(diào)用都會(huì)使用 Workbook 對(duì)象創(chuàng)建一個(gè) Style 對(duì)象,而我們的數(shù)據(jù)一共有 6W 條,沒條數(shù)據(jù)又有 6 個(gè)屬性,我們一共要渲染 36W 個(gè)單元格,也就是要生成 36W 個(gè) Style 對(duì)象。于是,在我運(yùn)行代碼時(shí)便出現(xiàn)了如下報(bào)錯(cuò)。
F:\java\jdk1.8.0_151\bin\java.exe Exception in thread "main" java.lang.IllegalStateException: The maximum number of Cell Styles was exceeded. You can define up to 64000 style in a .xlsx Workbookat org.apache.poi.xssf.model.StylesTable.createCellStyle(StylesTable.java:789)at org.apache.poi.xssf.usermodel.XSSFWorkbook.createCellStyle(XSSFWorkbook.java:682)at org.apache.poi.xssf.streaming.SXSSFWorkbook.createCellStyle(SXSSFWorkbook.java:869)at com.xhc.study.util.poi.ExcelFactory.getCommonStyle(ExcelFactory.java:114)at com.xhc.study.util.poi.ExcelFactory.createExcel(ExcelFactory.java:73)at Test.main(Test.java:62)Process finished with exit code 1復(fù)制代碼這里提示我們最多讓一個(gè) Workbook 對(duì)象生成 64000 個(gè) Style 對(duì)象。 以后一些危險(xiǎn)的操作還是少做?
導(dǎo)入
導(dǎo)入操作即使用 Java 讀取 Excel 中的數(shù)據(jù),常見場(chǎng)景是在頁(yè)面上點(diǎn)擊導(dǎo)入按鈕,用戶選擇 Excel 文件,其中可能是多條商品數(shù)據(jù)(包含編號(hào)、名稱、參數(shù)等信息),通過文件上傳功能將 Excel 讀取到我們的程序中,解析其中的數(shù)據(jù)并存入數(shù)據(jù)庫(kù)中。
讀取數(shù)據(jù)并打印
導(dǎo)入操作主要依靠 Workbook 的一個(gè)構(gòu)造函數(shù),源碼如下
/*** Constructs a XSSFWorkbook object, by buffering the whole stream into memory* and then opening an {@link OPCPackage} object for it.* * <p>Using an {@link InputStream} requires more memory than using a File, so* if a {@link File} is available then you should instead do something like* <pre><code>* OPCPackage pkg = OPCPackage.open(path);* XSSFWorkbook wb = new XSSFWorkbook(pkg);* // work with the wb object* ......* pkg.close(); // gracefully closes the underlying zip file* </code></pre>*/ public XSSFWorkbook(InputStream is) throws IOException {super(PackageHelper.open(is));beforeDocumentRead();// Build a tree of POIXMLDocumentParts, this workbook being the rootload(XSSFFactory.getInstance());// some broken Workbooks miss this...if(!workbook.isSetBookViews()) {CTBookViews bvs = workbook.addNewBookViews();CTBookView bv = bvs.addNewWorkbookView();bv.setActiveTab(0);} } 復(fù)制代碼從這個(gè)構(gòu)造函數(shù)來看,我們只需提供一個(gè)輸入流,便能構(gòu)造一個(gè) Workbook 對(duì)象出來,接下來我們首先寫一個(gè)處理 Workbook 的方法,參數(shù)為一個(gè) Workbook 對(duì)象,我們?cè)诜椒▋?nèi)部遍歷表格并輸出數(shù)據(jù),這里我們默認(rèn)該文件是一個(gè)規(guī)則的表格,即符合我們之前生成的 Excel 那樣的格式。代碼如下
/*** 讀取 Excel 數(shù)據(jù)并處理* @param workbook 完整的 Workbook 對(duì)象*/ public static void readExcel(Workbook workbook) {Sheet sheet = workbook.getSheetAt(0);//獲取總行數(shù)int rows = sheet.getPhysicalNumberOfRows();//去除表頭,從第 1 行開始打印for (int i = 0; i < rows; i++) {Row row = sheet.getRow(i);//獲取總列數(shù)int cols = row.getPhysicalNumberOfCells();for (int j = 0; j < cols; j++) {System.out.print(row.getCell(j) + "\t");}System.out.println();} } 復(fù)制代碼為了輸出方便,我已將 Excel 中的數(shù)據(jù)降為 100 條。調(diào)用代碼如下
FileInputStream in = new FileInputStream("F:\\testXSSF.xlsx"); XSSFWorkbook workbook = new XSSFWorkbook(in); ExcelFactory.readExcel(workbook); in.close(); 復(fù)制代碼輸出結(jié)果如下
數(shù)據(jù)已經(jīng)拿到,接下來的問題是解析為對(duì)象,畢竟我們平時(shí)向數(shù)據(jù)庫(kù)保存數(shù)據(jù)使用的 ORM 框架一般都使用了傳輸對(duì)象。這里我們?cè)俅卫梅瓷?#xff0c;完善代碼,使 readExcel 方法有讀取 Excel 中的數(shù)據(jù)并將其映射為對(duì)象的能力。
完善
這里需要明確幾個(gè)問題
接下來我們開始完善 readExcel 方法,代碼如下
/*** 讀取 Excel 數(shù)據(jù)并處理** @param workbook 完整的 Workbook 對(duì)象* @param clazz Excel 中存儲(chǔ)的數(shù)據(jù)的類的 Class 對(duì)象* @param <T> 泛型* @return 解析之后的對(duì)象列表,與泛型一致* @throws Exception*/ public static <T> List<T> readExcel(Workbook workbook, Class<T> clazz) throws Exception {List<T> list = new ArrayList<>();Sheet sheet = workbook.getSheetAt(0);//獲取總行數(shù)int rows = sheet.getPhysicalNumberOfRows();//獲取所有字段名String[] fieldsName = getFieldsName(clazz);Method[] methods = new Method[fieldsName.length];//去除表頭,從第 1 行開始打印for (int i = 1; i < rows; i++) {T obj = clazz.newInstance();Row row = sheet.getRow(i);//獲取總列數(shù)int cols = row.getPhysicalNumberOfCells();//獲取所有屬性Field[] fields = clazz.getDeclaredFields();//處理對(duì)象的每一個(gè)屬性for (int j = 0; j < cols; j++) {//第一次循環(huán)時(shí)初始化所有 setter 方法名if (i == 1) {String fieldName = fieldsName[j];//處理布爾值命名 "isXxx" -> "setXxx"if (fieldName.contains("is")) {fieldName = fieldName.split("is")[1];}methods[j] = obj.getClass().getMethod("set" +fieldName.substring(0, 1).toUpperCase() +fieldName.substring(1), fields[j].getType());}//先將單元格中的值按 String 保存String param = row.getCell(j).getStringCellValue();//屬性的類型String typeName = fields[j].getType().getSimpleName();//set 方法Method method = methods[j];//排除空值if (param == null || "".equals(param)) {continue;}//根據(jù)對(duì)象的不同屬性字段轉(zhuǎn)換單元格中的數(shù)據(jù)類型并調(diào)用 set 方法賦值if ("Integer".equals(typeName) || "int".equals(typeName)) {method.invoke(obj, Integer.parseInt(param));} else if ("Date".equals(typeName)) {String pattern;if (param.contains("CST")) {//java.util.Date 的默認(rèn)格式pattern = "EEE MMM dd HH:mm:ss zzz yyyy";} else if (param.contains(":")) {//帶有時(shí)分秒的格式pattern = "yyyy-MM-dd HH:mm:ss";} else {//簡(jiǎn)單格式pattern = "yyyy-MM-dd";}method.invoke(obj, new SimpleDateFormat(pattern, Locale.UK).parse(param));} else if ("Long".equalsIgnoreCase(typeName)) {method.invoke(obj, Long.parseLong(param));} else if ("Double".equalsIgnoreCase(typeName)) {method.invoke(obj, Double.parseDouble(param));} else if ("Boolean".equalsIgnoreCase(typeName)) {method.invoke(obj, Boolean.parseBoolean(param));} else if ("Short".equalsIgnoreCase(typeName)) {method.invoke(obj, Short.parseShort(param));} else if ("Character".equals(typeName) || "char".equals(typeName)) {method.invoke(obj, param.toCharArray()[0]);} else {//若數(shù)據(jù)格式為 String 則不必轉(zhuǎn)換method.invoke(obj, param);}}//不要忘記這句list.add(obj);}return list; } 復(fù)制代碼接下來我們改造 Person 類,添加幾個(gè)不同類型的數(shù)據(jù),并加入 toString() 方法,供我們測(cè)試使用。
import java.util.Date;/**** @author calmer* @since 2018/12/5 14:50*/ public class Person {private Integer id;private String name;private Integer age;private String hobby;private String job;private String address;private Date birthday;private Character sex;private Long phone;private Boolean isWorked;public String toString() {return "Person{" +"id=" + id +", name='" + name + '\'' +", age=" + age +", hobby='" + hobby + '\'' +", job='" + job + '\'' +", address='" + address + '\'' +", birthday=" + birthday +", sex=" + sex +", phone=" + phone +", isWorked=" + isWorked +'}';}public Long getPhone() {return phone;}public void setPhone(Long phone) {this.phone = phone;}public Boolean getWorked() {return isWorked;}public void setWorked(Boolean worked) {isWorked = worked;}public Character getSex() {return sex;}public void setSex(Character sex) {this.sex = sex;}public Date getBirthday() {return birthday;}public void setBirthday(Date birthday) {this.birthday = birthday;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getHobby() {return hobby;}public void setHobby(String hobby) {this.hobby = hobby;}public String getJob() {return job;}public void setJob(String job) {this.job = job;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;} }復(fù)制代碼接下來是測(cè)試調(diào)用的代碼,我們直接將導(dǎo)出與導(dǎo)入兩段代碼一起執(zhí)行。
public static void main(String[] args) throws Exception {//生成數(shù)據(jù)List<Person> list = new ArrayList<>();for (int i = 0; i < 100; i++) {int num = i + 1;Person person = new Person();person.setId(num);person.setName("張三-"+(num));person.setAddress("花園路"+num+"號(hào)"+(int)Math.ceil(Math.random()*10)+"號(hào)樓");person.setAge(i+18);person.setHobby("洗臉?biāo)⒀来駾OTA");person.setJob("程序員");person.setBirthday(new Date());person.setSex('男');person.setPhone(4536456498778789123L);person.setWorked(true);list.add(person);}//導(dǎo)出 ExcelFileOutputStream out = new FileOutputStream("F:\\testXSSF.xlsx");ExcelFactory.createExcel(list,ExcelType.SXSSF).write(out);out.close();//導(dǎo)入 ExcelFileInputStream in = new FileInputStream("F:\\testXSSF.xlsx");XSSFWorkbook workbook = new XSSFWorkbook(in);List<Person> personList = ExcelFactory.readExcel(workbook,Person.class);in.close();//遍歷結(jié)果for (Person person : personList) {System.out.println(person);} } 復(fù)制代碼執(zhí)行結(jié)果如下:
功能已經(jīng)基本實(shí)現(xiàn),我們這次再試一下在大數(shù)據(jù)導(dǎo)入的情景下,程序的耗時(shí)如何。我們這次同樣適用 6W 條數(shù)據(jù)。結(jié)果如下
這里我們可以看到,導(dǎo)入操作占用的內(nèi)存和耗時(shí),都比導(dǎo)出操作多很多。在導(dǎo)出的時(shí)候我們知道 POI 在導(dǎo)出大數(shù)據(jù)量的時(shí)候提供了 SXSSF 的方式解決耗時(shí)和內(nèi)存溢出問題,那么在導(dǎo)入時(shí)是不是也會(huì)有某種方式可以解決這個(gè)問題呢?
使用 Sax 事件驅(qū)動(dòng)解析
關(guān)于這部分的代碼,可以在網(wǎng)上找到許多,本次暫不討論。另外聽說有一個(gè) EasyExcel 挺好用的,有時(shí)間試一下。
感悟
通過這次探索,深知自己不足的地方還很多,原來寫代碼的時(shí)候考慮的太少,有關(guān)效率,內(nèi)存使用等方面的問題在自己測(cè)試的時(shí)候是看不出來的,真正使用的時(shí)候這些問題才會(huì)暴露出來,比如某項(xiàng)操作可能會(huì)導(dǎo)致用戶幾十秒甚至幾分鐘的等待,或者程序直接崩掉。 所以以后還是要小心謹(jǐn)慎,對(duì)工具類的使用不能會(huì)用就夠,要盡量的深入研究。 道可頓悟,事需漸修。
須知
- JDK 版本:1.8.0_151
- POI 版本:3.14
- 開發(fā)工具:IDEA
- 參考鏈接
- POI-HSSF and POI-XSSF/SXSSF
- POI讀寫大數(shù)據(jù)量excel,解決超過幾萬行而導(dǎo)致內(nèi)存溢出的問題
- EasyExcel
轉(zhuǎn)載于:https://juejin.im/post/5c09e559e51d451da152df9c
總結(jié)
以上是生活随笔為你收集整理的Java 使用 POI 操作 Excel的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 非结构化数据与结构化数据提取---- B
- 下一篇: java美元兑换,(Java实现) 美元