Java 使用 POI 操做 Excel

Apache POI 基本介绍

Apache POI 是 Apache 软件基金会提供的 100% 开源库。支持 Excel 库的全部基本功能。html

image.png | left | 629x536

图片来源:易百教程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

使用 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();
复制代码

生成结果:数据结构

image.png | left | 747x402

image.png | left | 747x223

问题

以上代码已经完成简单的 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();
    复制代码

    生成的结果以下

    image.png | left | 747x402

    image.png | left | 747x402

    其余

    这里测试的时候我使用6W的数据,因此程序进行的比较慢,用时以下:

    image.png | left | 387x113

    像这种大数据量的导出,咱们可使用 SXSSF 的方式,网上也有不少例子,官网的对比。使用 SXSSF 方式导出用时以下:

    image.png | left | 405x114

    能够看到时间缩短了不少。接下来咱们单独来了解一下如何控制表格的样式。

    样式

    一般,咱们须要控制的样式有两个部分,一个是表头部分的样式,另外一个是普通单元格的样式。此次咱们就仅建立两个方法演示样式的设置方式。 在 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;
    }
    复制代码

    生成结果以下(忽视颜色搭配与美观程度)

    image.png | left | 747x402

    注意

    这里我运行的出了一个问题,在此记录。 注意上面代码的第 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();
    复制代码

    输出结果以下

    image.png | left | 638x223

    image.png | left | 581x210

    数据已经拿到,接下来的问题是解析为对象,毕竟咱们平时向数据库保存数据使用的 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;
    
        @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);
        }
    }
    复制代码

    执行结果以下:

    image.png | left | 747x113

    image.png | left | 747x135

    功能已经基本实现,咱们此次再试一下在大数据导入的情景下,程序的耗时如何。咱们此次一样适用 6W 条数据。结果以下

    image.png | left | 516x170

    这里咱们能够看到,导入操做占用的内存和耗时,都比导出操做多不少。在导出的时候咱们知道 POI 在导出大数据量的时候提供了 SXSSF 的方式解决耗时和内存溢出问题,那么在导入时是否是也会有某种方式能够解决这个问题呢?

    使用 Sax 事件驱动解析

    关于这部分的代码,能够在网上找到许多,本次暂不讨论。另外据说有一个 EasyExcel 挺好用的,有时间试一下。

    感悟

    经过此次探索,深知本身不足的地方还不少,原来写代码的时候考虑的太少,有关效率,内存使用等方面的问题在本身测试的时候是看不出来的,真正使用的时候这些问题才会暴露出来,好比某项操做可能会致使用户几十秒甚至几分钟的等待,或者程序直接崩掉。 因此之后仍是要当心谨慎,对工具类的使用不能会用就够,要尽可能的深刻研究。 道可顿悟,事需渐修。

    须知

相关文章
相关标签/搜索