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.mzph.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博客_百度网盘数据库最近研究了一下百度网盘的本地数据库文件…