Java 使用 POI 操作 Excel

Apache POI 基本介紹

Apache POI 是 Apache 軟件基金會提供的 100% 開源庫。支持 Excel 庫的所有基本功能。

圖片來源:易百教程

基本概念

在 POI 中,Workbook代表著一個 Excel 文件(工作簿),Sheet代表著 Workbook 中的一個表格,Row 代表 Sheet 中的一行,而 Cell 代表著一個單元格。 HSSFWorkbook對應的就是一個 .xls 文件,兼容 Office97-2003 版本。 XSSFWorkbook對應的是一個 .xlsx 文件,兼容 Office2007 及以上版本。 在 HSSFWorkbook 中,Sheet接口 的實現類為 HSSFSheet,Row接口 的實現類為HSSFRow,Cell 接口的實現類為 HSSFCell。 XSSFWorkbook 中實現類的命名方式類似,在 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>
復制代碼

使用 POI

使用 POI 的目的就是為了在 Java 中解析/操作 Excel 表格,實現 Excel 的導入/導出的功能,接下來我們依次來看它們的實現代碼及注意事項。

導出

導出操作即使用 Java 寫出數據到 Excel 中,常見場景是將頁面上的數據(可能是經過條件查詢的)導出,這些數據可能是財務數據,也可能是商品數據,生成 Excel 后返回給用戶下載文件。 該操作主要涉及 Excel 的創建及使用流輸出的操作,在 Excel 創建過程中,可能還涉及到單元格樣式的操作。

創建并導出基本數據

進行導出操作的第一步是創建 Excel 文件,我們寫一個方法,參數是需要寫入 Excel 表格的數據和生成 Excel 方式(HSSF,XSSF),返回一個 Workbook 接口對象。 在方法內部我們采用反射來創建 Workbook 的實例對象。

代碼

探索階段,我們先將數據類型限定為 List,并把列數限定為某個數字,生成一個表格。 代碼如下:

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 工廠類,負責 Workbook 的生成和解析** @author calmer* @since 2018/12/5 11:19*/
public class ExcelFactory {/*** 構造 Workbook 對象,具體實例化哪種對象由 type 參數指定* @param data 要導出的數據* @param type Excel 生成方式* @return 對應 type 的工作簿實例對象* @throws Exception 反射生成對象時出現的異常* <li>InstantiationException</li>* <li>IllegalAccessException</li>* <li>InstantiationException</li>*/public static Workbook createExcel(List data,String type) throws Exception{//根據 type 參數生成工作簿實例對象Workbook workbook = (Workbook) Class.forName(type).newInstance();//這里還可以指定 sheet 的名字//Sheet sheet = workbook.createSheet("sheetName");Sheet sheet = workbook.createSheet();// 限定列數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;}
}
復制代碼

調用時,我們生成好數據并構造好 Workbook 對象,再調用 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();
復制代碼

生成結果:

問題

以上代碼已經完成簡單的 Excel 文件生成操作,但其中還有幾點問題沒有解決

  • 實際場景下,Excel 表格中可能并不會存 Integer、String 這種基本數據結構的數據,更多的可能是對象數據(JSON、List),需要有表頭,并將對象對應的屬性一行行的顯示出來(參考數據庫查詢語句執行的結果)。并且表頭的樣式一定是要控制的。
  • 我們并沒有對方法中 type 屬性進行限制,即外部可以傳來任何類似“a”、“b”這樣的無效值,屆時程序會拋出異常,可以使用靜態常量或枚舉類來限定,這樣可以增強代碼可讀性和健壯性。這里我并不想用靜態常量或枚舉類,打算使用注解的方式來控制參數的有效性。

完善

我們已經明確了兩個問題:

  1. 之前的程序并不能在實際場景使用,我們需要將其完善到具有處理實際數據的能力。
  2. 利用注解限定參數的有效性。

我們先來解決第二個問題,即參數的問題。

使用注解限定參數

首先創建一個注解類

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;/**** @author calmer* @since 2018/12/5 12:27*/
@Retention(RetentionPolicy.SOURCE)
public @interface ExcelType {String HSSF = "org.apache.poi.hssf.usermodel.HSSFWorkbook";String XSSF = "org.apache.poi.xssf.usermodel.XSSFWorkbook";
}復制代碼

在方法參數上加上注解

public static Workbook createExcel(List data, @ExcelType String type) throws Exception {//內容省略}
復制代碼

調用時

ExcelFactory.createExcel(list,ExcelType.HSSF).write(out);
復制代碼

關于使用注解來限定參數的取值范圍這種方式,我也是偶然看到過,可是這種方式在我這里編譯器并不會給任何提示,我對注解了解不夠,以后有機會要再好好研究一下。

解決實際數據問題

在實際應用中,很常見的情況是我們有很多實體類,比如 Person,Product,Order 等,借助反射,我們可以獲取任意實體類的屬性列表、getter 方法,所以目前,我打算利用反射,來處理多個對象的 Excel 導出。 首先我們創建一個方法,用來獲取某個對象的屬性列表(暫時不考慮要獲取父類屬性的情況)。

/*** 獲取對象的屬性名數組* @param clazz Class 對象,用于獲取該類的信息* @return 該類的所有屬性名數組*/
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;
}
復制代碼

然后我們完善 createExcel() 方法

public static Workbook createExcel(List data, @ExcelType String type) throws Exception {if(data == null || data.size() == 0){throw new Exception("數據不能為空");}//根據類型生成工作簿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++) {//加載第一行數據時,初始化所有屬性的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;
}
復制代碼

測試

以上代碼基本滿足一開始的需求,即以類的屬性名為表頭并生成表格。接下來我們生成一定量的數據,并測試導出效果。 實體類代碼

/**** @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;}
}
復制代碼

測試類代碼

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+"號"+(int)Math.ceil(Math.random()*10)+"號樓");person.setAge(i+18);person.setHobby("洗臉刷牙打DOTA");person.setJob("程序員");list.add(person);
}
FileOutputStream out = new FileOutputStream("F:\\testXSSF.xlsx");
ExcelFactory.createExcel(list,ExcelType.XSSF).write(out);
out.close();
復制代碼

生成的結果如下

其他

這里測試的時候我使用6W的數據,所以程序進行的比較慢,用時如下:

像這種大數據量的導出,我們可以使用 SXSSF 的方式,網上也有很多例子,官網的對比。使用 SXSSF 方式導出用時如下:

可以看到時間縮短了很多。接下來我們單獨來了解一下如何控制表格的樣式。

樣式

通常,我們需要控制的樣式有兩個部分,一個是表頭部分的樣式,另一個是普通單元格的樣式。這次我們就僅創建兩個方法演示樣式的設置方式。 在 POI 中,控制單元格樣式的對象是 CellStyle 接口,可以通過 Workbook 的createStyle 方法獲得實例對象,這里我們寫一個方法設置表頭的樣式。

private static CellStyle getTheadStyle(Workbook workbook){CellStyle style = workbook.createCellStyle();//設置填充色style.setFillForegroundColor(IndexedColors.LIGHT_BLUE.index);style.setFillPattern(CellStyle.SOLID_FOREGROUND);//設置對齊方式style.setAlignment(CellStyle.ALIGN_CENTER);//字體樣式Font font = workbook.createFont();//設置字體名稱font.setFontName("華文隸書");//斜體font.setItalic(true);//字體顏色font.setColor(IndexedColors.YELLOW.index);//字體大小font.setFontHeightInPoints((short)12);//不要忘記這句style.setFont(font);return style;
}
復制代碼

調用

Row thead = sheet.createRow(0);
//設置行高
thead.setHeight((short) 500);
//僅使用 setRowStyle 方法會對除有值的表頭設置樣式
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]);//在這里循環為每個有值的表頭設置樣式。//結合上面的 setRowStyle 會將表頭行全部設置樣式cell.setCellStyle(style);
}
復制代碼

接下來我們寫獲取普通單元格樣式的方法

private static CellStyle getCommonStyle(Workbook workbook){CellStyle style = workbook.createCellStyle();//設置填充色style.setFillForegroundColor(IndexedColors.GREEN.index);style.setFillPattern(CellStyle.SOLID_FOREGROUND);//設置居中對齊style.setAlignment(CellStyle.ALIGN_CENTER);Font font = workbook.createFont();font.setFontName("華文彩云");//不要忘記這句style.setFont(font);return style;
}
復制代碼

完整調用

public static Workbook createExcel(List data, @ExcelType String type) throws Exception {if(data == null || data.size() == 0){throw new Exception("數據不能為空");}//根據類型生成工作簿Workbook workbook = (Workbook) Class.forName(type).newInstance();//生成樣式CellStyle style = getTheadStyle(workbook);//新建表格Sheet sheet = workbook.createSheet();//生成表頭Row thead = sheet.createRow(0);//設置行高thead.setHeight((short) 500);//僅使用 setRowStyle 方法會對除有值的表頭設置樣式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]);//在這里循環為每個有值的表頭設置樣式。//結合上面的 setRowStyle 會將表頭行全部設置樣式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++) {//加載第一行數據時,初始化所有屬性的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());//設置單元格樣式cell.setCellStyle(style);}}return workbook;
}
復制代碼

生成結果如下(忽視顏色搭配與美觀程度)

注意

這里我運行的出了一個問題,在此記錄。 注意上面代碼的第 28 行和第 48 行,這里我們在 for 循環外面獲取 Style 對象,在 for 循環中循環設置單元格樣式的時候,始終使用的是__同一個__ Style。而最開始我測試的時候,并不是這樣寫,而是像下面這樣:

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++) {//加載第一行數據時,初始化所有屬性的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());//設置單元格樣式cell.setCellStyle(getCommonStyle(workbook));}
}
復制代碼

注意 20 行,在 getCommonStyle 方法中,我們每次調用都會使用 Workbook 對象創建一個 Style 對象,而我們的數據一共有 6W 條,沒條數據又有 6 個屬性,我們一共要渲染 36W 個單元格,也就是要生成 36W 個 Style 對象。于是,在我運行代碼時便出現了如下報錯。

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復制代碼

這里提示我們最多讓一個 Workbook 對象生成 64000 個 Style 對象。 以后一些危險的操作還是少做?

導入

導入操作即使用 Java 讀取 Excel 中的數據,常見場景是在頁面上點擊導入按鈕,用戶選擇 Excel 文件,其中可能是多條商品數據(包含編號、名稱、參數等信息),通過文件上傳功能將 Excel 讀取到我們的程序中,解析其中的數據并存入數據庫中。

讀取數據并打印

導入操作主要依靠 Workbook 的一個構造函數,源碼如下

/*** 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);}
}
復制代碼

從這個構造函數來看,我們只需提供一個輸入流,便能構造一個 Workbook 對象出來,接下來我們首先寫一個處理 Workbook 的方法,參數為一個 Workbook 對象,我們在方法內部遍歷表格并輸出數據,這里我們默認該文件是一個規則的表格,即符合我們之前生成的 Excel 那樣的格式。代碼如下

/*** 讀取 Excel 數據并處理* @param workbook 完整的 Workbook 對象*/
public static void readExcel(Workbook workbook) {Sheet sheet = workbook.getSheetAt(0);//獲取總行數int rows = sheet.getPhysicalNumberOfRows();//去除表頭,從第 1 行開始打印for (int i = 0; i < rows; i++) {Row row = sheet.getRow(i);//獲取總列數int cols = row.getPhysicalNumberOfCells();for (int j = 0; j < cols; j++) {System.out.print(row.getCell(j) + "\t");}System.out.println();}
}
復制代碼

為了輸出方便,我已將 Excel 中的數據降為 100 條。調用代碼如下

FileInputStream in = new FileInputStream("F:\\testXSSF.xlsx");
XSSFWorkbook workbook = new XSSFWorkbook(in);
ExcelFactory.readExcel(workbook);
in.close();
復制代碼

輸出結果如下

數據已經拿到,接下來的問題是解析為對象,畢竟我們平時向數據庫保存數據使用的 ORM 框架一般都使用了傳輸對象。這里我們再次利用反射,完善代碼,使 readExcel 方法有讀取 Excel 中的數據并將其映射為對象的能力。

完善

這里需要明確幾個問題

  1. 如何確定對象?
  2. 如何將未知對象的每個字段的數據類型與 Excel 表格中的字符串數據進行轉換?
  3. 出現空值我們如何解決?
  4. 日期格式的數據我們如何轉換?有幾種日期格式?

接下來我們開始完善 readExcel 方法,代碼如下

/*** 讀取 Excel 數據并處理** @param workbook 完整的 Workbook 對象* @param clazz    Excel 中存儲的數據的類的 Class 對象* @param <T>      泛型* @return 解析之后的對象列表,與泛型一致* @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);//獲取總行數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);//獲取總列數int cols = row.getPhysicalNumberOfCells();//獲取所有屬性Field[] fields = clazz.getDeclaredFields();//處理對象的每一個屬性for (int j = 0; j < cols; j++) {//第一次循環時初始化所有 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;}//根據對象的不同屬性字段轉換單元格中的數據類型并調用 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 的默認格式pattern = "EEE MMM dd HH:mm:ss zzz yyyy";} else if (param.contains(":")) {//帶有時分秒的格式pattern = "yyyy-MM-dd HH:mm:ss";} else {//簡單格式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 {//若數據格式為 String 則不必轉換method.invoke(obj, param);}}//不要忘記這句list.add(obj);}return list;
}
復制代碼

接下來我們改造 Person 類,添加幾個不同類型的數據,并加入 toString() 方法,供我們測試使用。

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;@Overridepublic 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;}
}復制代碼

接下來是測試調用的代碼,我們直接將導出與導入兩段代碼一起執行。

public static void main(String[] args) throws Exception {//生成數據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+"號"+(int)Math.ceil(Math.random()*10)+"號樓");person.setAge(i+18);person.setHobby("洗臉刷牙打DOTA");person.setJob("程序員");person.setBirthday(new Date());person.setSex('男');person.setPhone(4536456498778789123L);person.setWorked(true);list.add(person);}//導出 ExcelFileOutputStream out = new FileOutputStream("F:\\testXSSF.xlsx");ExcelFactory.createExcel(list,ExcelType.SXSSF).write(out);out.close();//導入 ExcelFileInputStream in = new FileInputStream("F:\\testXSSF.xlsx");XSSFWorkbook workbook = new XSSFWorkbook(in);List<Person> personList = ExcelFactory.readExcel(workbook,Person.class);in.close();//遍歷結果for (Person person : personList) {System.out.println(person);}
}
復制代碼

執行結果如下:

功能已經基本實現,我們這次再試一下在大數據導入的情景下,程序的耗時如何。我們這次同樣適用 6W 條數據。結果如下

這里我們可以看到,導入操作占用的內存和耗時,都比導出操作多很多。在導出的時候我們知道 POI 在導出大數據量的時候提供了 SXSSF 的方式解決耗時和內存溢出問題,那么在導入時是不是也會有某種方式可以解決這個問題呢?

使用 Sax 事件驅動解析

關于這部分的代碼,可以在網上找到許多,本次暫不討論。另外聽說有一個 EasyExcel 挺好用的,有時間試一下。

感悟

通過這次探索,深知自己不足的地方還很多,原來寫代碼的時候考慮的太少,有關效率,內存使用等方面的問題在自己測試的時候是看不出來的,真正使用的時候這些問題才會暴露出來,比如某項操作可能會導致用戶幾十秒甚至幾分鐘的等待,或者程序直接崩掉。 所以以后還是要小心謹慎,對工具類的使用不能會用就夠,要盡量的深入研究。 道可頓悟,事需漸修。

須知

  • JDK 版本:1.8.0_151
  • POI 版本:3.14
  • 開發工具:IDEA
  • 參考鏈接
    • POI-HSSF and POI-XSSF/SXSSF
    • POI讀寫大數據量excel,解決超過幾萬行而導致內存溢出的問題
    • EasyExcel

轉載于:https://juejin.im/post/5c09e559e51d451da152df9c

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/537349.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/537349.shtml
英文地址,請注明出處:http://en.pswp.cn/news/537349.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Microsoft Project 變更項目日歷的注意事項

場景 今天在修改一份mpp排期計劃里的項目日歷&#xff0c;日歷是設置了周末2天加班&#xff0c;變更后發現&#xff0c;排期縮短的天數不對。一開始以為是Project軟件出問題了&#xff0c;與windows11不兼容&#xff0c;重啟了電腦&#xff0c;重試后還是這樣&#xff0c;后來…

python共享單車案例分析_python分析數據分析項目:共享單車租用情況影響因素探索分析...

python分析數據分析項目&#xff1a;共享單車租用情況影響因素探索分析

第十九節TypeScript 模塊

1、TypeScript模塊&#xff1a; 模塊是在其自身的作用域里執行&#xff0c;并不是在全局作用域&#xff0c;這意味著定義在模塊里面的變量、函數和類等在模塊外部是不可見的&#xff0c;除非明確地使用 export 導出它們。類似地&#xff0c;我們必須通過 import 導入其他模塊導…

微軟宣布 Edge 瀏覽器將切換至 Chromium 內核

簡述 據微軟官方 blog的消息&#xff0c;windows 的默認瀏覽器 Edge將切換內核至 Chromium&#xff0c;并且微軟將秉承開源精神&#xff0c;在未來更多的為 Chromium項目貢獻代碼。 微軟具體說了什么&#xff1f; 原文如下&#xff1a; For the past few years, Microsoft has …

解決the resource is not on the build path of a java project

場景 普通的Java project 轉maven 項目后&#xff0c;導入類提示&#xff1a;the resource is not on the build path of a java project 解決方法 將檢查java build source&#xff0c;將報錯的刪除&#xff0c;重新添加src資源包

python實現按回車鍵繼續程序_python實現按任意鍵繼續執行程序

在windows下寫bat的時候&#xff0c;通過pause命令&#xff0c;可以暫停程序運行&#xff0c;例如經常見的程序會在終端提示”按任意鍵繼續……”,用戶在終端回車后程序可以接著運行&#xff0c;這個功能有多大用途今天暫且不說&#xff0c;但我覺得應該有很多人也想在python下…

Beta

目錄 過去存在的問題任務分工規范后端總結卉卉家燦前端總結緒佩青元愷琳宇恒丹丹算法&API接口家偉鴻杰一好文檔&博客撰寫政演產品功能我們已經坐了哪些調整桌面控件合并我們會在Beta沖刺中做哪些改進組長博客&#xff1a;https://www.cnblogs.com/heihuifei/p/10084535…

項目驗收文檔合并技巧

1&#xff09;先將各個文件word寫好&#xff0c;格式&#xff0c;字體調整好&#xff1b; 2&#xff09;每個word導出PDF&#xff1b; 3&#xff09;將導出的PDF合并到一個大的PDF&#xff1b;

xsl判斷節點存在_HashMap1.8之節點刪除分析

HashMap之節點刪除大家一直關注的都是HashMap如何添加節點&#xff0c;當節點數量大于8的時候轉化為紅黑樹&#xff0c;否則使用鏈表等等&#xff0c;但大家是否有看過刪除節點的處理邏輯呢&#xff1f; 今天來看看HashMap刪除節點的神來之筆問題來源在查看HashMap源碼時&#…

用Emit技術替代反射

System.Reflection.Emit命名空間類可用于動態發出Microsoft中間語言&#xff08;MSIL&#xff09;代碼&#xff0c;以便生成的代碼可以直接執行。反射也用于獲取有關類及其成員的信息。換句話說&#xff0c;反射是一種技術&#xff0c;允許您檢查描述類型及其成員的元數據&…

windows安裝TortoiseGit詳細使用教程

windows安裝TortoiseGit詳細使用教程【基礎篇】_小飛牛的技術博客_51CTO博客windows安裝TortoiseGit詳細使用教程【基礎篇】&#xff0c;環境&#xff1a;win8.164bit安裝準備&#xff1a;首先你得安裝windows下的git msysgit1.9.5安裝版本控制器客戶端tortoisegit tortoisegit…

keras中文文檔_【DL項目實戰02】圖像識別分類——Keras框架+卷積神經網絡CNN(使用VGGNet)

版權聲明&#xff1a;小博主水平有限&#xff0c;希望大家多多指導。目錄&#xff1a;【使用傳統DNN】BG大龍&#xff1a;【DL項目實戰02】圖像分類——Keras框架使用傳統神經網絡DNN?zhuanlan.zhihu.com【使用卷積神經網絡CNN】BG大龍&#xff1a;【DL項目實戰02】圖像識別分…

Java Html轉pdf實戰

Java Html轉pdf實戰 - 簡書年尾手頭沒啥事&#xff0c;干起了打雜工作&#xff0c;最近幫忙解決后端項目里一個html批量轉pdf速度慢的問題&#xff0c;項目里用到的轉換工具是 wkhtmltopdf &#xff0c;這貨轉單個html還好&#xff0c;批量轉速...https://www.jianshu.com/p/d0…

Hadoop生態圈-Ambari控制臺功能簡介

Hadoop生態圈-Ambari控制臺功能簡介 作者&#xff1a;尹正杰 版權聲明&#xff1a;原創作品&#xff0c;謝絕轉載&#xff01;否則將追究法律責任。 在經歷一系列安裝過程之后&#xff08;部署過HDP后我終于發現為什么大家喜歡用它了&#xff0c;部署比CDH簡單是他優勢之一&…

oracle監聽啟動很慢

TNS-12531: TNS:cannot allocate memory 首先查看內存&#xff0c;free -m 發現當前的空閑內存還有很多&#xff0c;那就不是內存不足的問題 想到之前重啟過數據庫服務器&#xff0c;查看主機名hostname,然后在查看etc/hosts 中的主機名&#xff0c;發現兩者不一致&#xff0c;…

python地圖標注_Python 給定的經緯度標注在地圖上的實現方法

博主最近發現了python中一個好玩的包叫basemap,使用這個包可以繪制地圖。值得說一下的是&#xff0c;basemap還沒有pip檢索&#xff0c;因此不能直接使用pip install basemap&#xff0c;來安裝這個包。所以需要自己把下面兩個包自行下載&#xff0c;然后在該目錄下使用pip安裝…

剪映專業版PC端清理緩存與日志

清理緩存 這個簡單&#xff0c;在全局設置里&#xff0c;點擊刪除鍵&#xff0c;就可以 清理日志 軟件每次剪輯都會生成日志&#xff0c;日志路徑在 C:\Users\zengm\AppData\Local\JianyingPro\User Data\Log C:\Users\zengm\AppData\Local\JianyingPro\User Data\VELog

nodejs源碼_nodejs之setTimeout源碼解析

setTimeout是在系統啟動的時候掛載的全局函數。代碼在timer.js。function setupGlobalTimeouts() {const timers NativeModule.require(timers);global.clearImmediate timers.clearImmediate;global.clearInterval timers.clearInterval;global.clearTimeout timers.clear…

百度網盤PC端緩存文件夾

在C:\Users\zengm\AppData\Roaming\baidu\BaiduNetdisk\users\下面 BaiduYunCacheFileV0.db 文件為百度網盤目錄數據&#xff0c;結構為&#xff1a; 百度網盤BaiduYunCacheFileV0.db數據庫研究_wqq1027的博客-CSDN博客_百度網盤數據庫最近研究了一下百度網盤的本地數據庫文件…