Apache POI 是 Apache 软件基金会提供的 100% 开源库。支持 Excel 库的全部基本功能。html
图片来源:易百教程java
在 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 前缀便可。git
<!-- 基本依赖,仅操做 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 的目的就是为了在 Java 中解析/操做 Excel 表格,实现 Excel 的导入/导出的功能,接下来咱们依次来看它们的实现代码及注意事项。程序员
导出操做即便用 Java 写出数据到 Excel 中,常见场景是将页面上的数据(多是通过条件查询的)导出,这些数据多是财务数据,也多是商品数据,生成 Excel 后返回给用户下载文件。 该操做主要涉及 Excel 的建立及使用流输出的操做,在 Excel 建立过程当中,可能还涉及到单元格样式的操做。github
进行导出操做的第一步是建立 Excel 文件,咱们写一个方法,参数是须要写入 Excel 表格的数据和生成 Excel 方式(HSSF,XSSF),返回一个 Workbook 接口对象。 在方法内部咱们采用反射来建立 Workbook 的实例对象。数据库
探索阶段,咱们先将数据类型限定为 List,并把列数限定为某个数字,生成一个表格。 代码以下:apache
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 文件生成操做,但其中还有几点问题没有解决框架
咱们已经明确了两个问题:
咱们先来解决第二个问题,即参数的问题。
首先建立一个注解类
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 Workbook
at 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 root
load(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 中的数据并将其映射为对象的能力。
这里须要明确几个问题
接下来咱们开始完善 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;
@Override
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;
}
}
复制代码
接下来是测试调用的代码,咱们直接将导出与导入两段代码一块儿执行。
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);
}
//导出 Excel
FileOutputStream out = new FileOutputStream("F:\\testXSSF.xlsx");
ExcelFactory.createExcel(list,ExcelType.SXSSF).write(out);
out.close();
//导入 Excel
FileInputStream 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 的方式解决耗时和内存溢出问题,那么在导入时是否是也会有某种方式能够解决这个问题呢?
关于这部分的代码,能够在网上找到许多,本次暂不讨论。另外据说有一个 EasyExcel 挺好用的,有时间试一下。
经过此次探索,深知本身不足的地方还不少,原来写代码的时候考虑的太少,有关效率,内存使用等方面的问题在本身测试的时候是看不出来的,真正使用的时候这些问题才会暴露出来,好比某项操做可能会致使用户几十秒甚至几分钟的等待,或者程序直接崩掉。 因此之后仍是要当心谨慎,对工具类的使用不能会用就够,要尽可能的深刻研究。 道可顿悟,事需渐修。