POI和EasyExcel


POI.

官网:https://poi.apache.org/

Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能

基本功能.

结构:

HSSF - 提供读写[Microsoft Excel](https://baike.baidu.com/item/Microsoft Excel)格式档案的功能。

XSSF - 提供读写Microsoft Excel OOXML格式档案的功能。

HWPF - 提供读写[Microsoft Word](https://baike.baidu.com/item/Microsoft Word)格式档案的功能。

HSLF - 提供读写Microsoft PowerPoint格式档案的功能。

HDGF - 提供读写[Microsoft Visio](https://baike.baidu.com/item/Microsoft Visio)格式档案的功能。

准备工作:.

导入依赖.

<!-- xls 03 -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>4.1.2</version>
</dependency>
<!-- xlsx 07 -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>4.1.2</version>
</dependency>
<!-- 日期格式化工具 -->
<dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
    <version>2.10.4</version>
</dependency>
<!-- test -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13</version>
</dependency>

Excel对象简单介绍.

Workbook : 工作簿
Sheet : 工作页
Row : 行
Cell : 单元格

Excel操作方式介绍.

POI提供了HSSF、XSSF以及SXSSF三种方式操作Excel

HSSF

Excel97-2003版本,扩展名为.xls。一个sheet最大行数65536,最大列数256。

缺点:最多只能处理65536行,否则会抛出异常  
优点:过程中写入缓存,不操作磁盘,最后一次性写入磁盘,速度快

XSSF

Excel2007版本开始,扩展名为.xlsx。一个sheet最大行数1048576,最大列数16384。

缺点:写数据时速度非常慢,非常耗内存,也会发生内存溢出,如100万条  
优点:可以写较大的数据量,如20万条

SXSSF

是在XSSF基础上,POI3.8版本开始提供的支持低内存占用的操作方式,扩展名为.xlsx。

优点:可以写非常大的数据量,如100万条甚至更多条,写数据速度快,占用更少的内存
注意:
- 过程中会产生临时文件,需要清理临时文件
- 默认由100条记录被保存在内存中,如果超过这数量,则最前面的数据被写入临时文件
- 如果想自定义内存中数据的数量,可以使用new SXSSFWorkbook ( 数量 )

SXSSFWorkbook-来至官方的解释:实现“BigGridDemo”策略的流式XSSFWorkbook版本。这允许写入非常大的文件而不会耗尽内存,因为任何时候只有可配置的行部分被保存在内存中。
请注意,仍然可能会消耗大量内存,这些内存基于您正在使用的功能,例如合并区域,注释……仍然只存储在内存中,因此如果广泛使用,可能需要大量内存。

POI-Excel写.

v03简单使用.

package read._03;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.junit.Test;

import java.io.*;

// poi(03版) 进行 Excel 写操作
public class ExcelWrite {
    private final String V03_OUT = "v03_out.xls";
    private final String resourcePath = System.getProperty("user.dir") + "\\src\\main\\resources\\";


    @Test
    public void testWrite() throws IOException {
        // 1、创建一个Excel03 工作簿对象
        HSSFWorkbook workbook = new HSSFWorkbook();

        // 2.创建工作表对象
        HSSFSheet poi_user = workbook.createSheet("poi_user");

        // 3.创建 行对象
        HSSFRow row = poi_user.createRow(0);// 第一行

        // 4.创建 单元格
        HSSFCell A1 = row.createCell(0);
        A1.setCellValue("A1"); // 字符串

        HSSFCell B1 = row.createCell(1);
        B1.setCellValue(12); // 整型

        HSSFCell C1 = row.createCell(2);
        C1.setCellValue(13.13); // 浮点

        HSSFCell D1 = row.createCell(3);
        String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        String datetime = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
        D1.setCellValue(datetime); // 时间(字符串格式)


        // 文件输出流
        FileOutputStream fos = new FileOutputStream(new File(resourcePath + V03_OUT)); // 使用完关闭流

        // 5.写出数据到文件
        workbook.write(fos);

        // 关闭流
        fos.close();
    }
}

v07简单使用.

// poi(07版) 进行 Excel 写操作
public class ExcelWrite {
    private final String V07_OUT = "v07_out.xlsx";
    private final String resourcePath = System.getProperty("user.dir") + "\\src\\main\\resources\\";

    @Test
    public void testWrite() throws IOException {
        // 1.获取工作簿对象 v07
        XSSFWorkbook workbook = new XSSFWorkbook();

        // 2.获取工作表
        XSSFSheet poi_user = workbook.createSheet("poi_user");

        // 3.获取行
        XSSFRow row = poi_user.createRow(1);// 第二行

        // 4.单元格
        XSSFCell A2 = row.createCell(0);
        A2.setCellValue("A2"); // 字符串

        XSSFCell B2 = row.createCell(1);
        B2.setCellValue(22); // 整型

        XSSFCell C2 = row.createCell(2);
        C2.setCellValue(23.23); // 浮点

        XSSFCell D2 = row.createCell(3);
        String datetime = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
        D2.setCellValue(datetime); // 时间字符串

        // 文件输出流
        FileOutputStream fos = new FileOutputStream(new File(resourcePath+V07_OUT));

        // 5.写出数据到文件
        workbook.write(fos);

        // 关闭流
        fos.close();

    }
}

v03 HSSF大文件写.

// poi 03版 HSSF 大文件(65536行)写demo
public class ExcelWrite {
    private final String V03_HSSF_BIGFILE = "v03_hssf_bigfile_out.xls";
    private String resourcePath = System.getProperty("user.dir") + "\\src\\main\\resources\\";

    @Test
    public void testWrite() throws IOException {
        long startTime = System.currentTimeMillis();

        // 1.获取工作簿
        HSSFWorkbook workbook = new HSSFWorkbook();

        // 2.创建工作表
        HSSFSheet hssf = workbook.createSheet("hssf");

        // 3.创建行(共65536行,如果超过,会报错)
        for (int i = 0; i < 65537 ; i++) {
            HSSFRow row = hssf.createRow(i);
            // 4.创建单元格,写入数据
            for (int j = 0; j < 5; j++) {
                HSSFCell cell = row.createCell(j);
                cell.setCellValue(""+i+j);
            }
        }

        // 文件输出流
        FileOutputStream fos = new FileOutputStream(new File(resourcePath+V03_HSSF_BIGFILE));

        // 5.写出数据到文件
        workbook.write(fos);

        // 关闭流
        fos.close();
        long endTime = System.currentTimeMillis();
        System.out.println("hssf大文件写耗费时间:" + (endTime - startTime) + "ms");

    }
}

java.lang.IllegalArgumentException: Invalid row number (65536) outside allowable range (0..65535) // 当写入数据行数超过65536时,抛出异常

v07 XSSF大文件写.

// poi 07版 XSSF 大文件(65537行)写demo
public class ExcelWrite {
    private final String V07_XSSF_BIGFILE = "v07_xssf_bigfile_out.xlsx";
    private String resourcePath = System.getProperty("user.dir") + "\\src\\main\\resources\\";

    @Test
    public void testWrite() throws IOException {
        long startTime = System.currentTimeMillis();

        // 1.获取工作簿
        XSSFWorkbook workbook = new XSSFWorkbook();

        // 2.创建工作表
        XSSFSheet xssf = workbook.createSheet("xssf");

        // 3.创建行(共65537行)
        for (int i = 0; i < 65537; i++) {
            XSSFRow row = xssf.createRow(i);
            // 4.创建单元格
            for (int j = 0; j < 5; j++) {
                XSSFCell cell = row.createCell(j);
                cell.setCellValue(""+i+j);
            }
        }

        // 创建文件输出流
        FileOutputStream fos = new FileOutputStream(new File(resourcePath+V07_XSSF_BIGFILE));

        // 5.输出数据到文件
        workbook.write(fos);

        // 关闭流
        fos.close();

        long endTime = System.currentTimeMillis();
        System.out.println("xssf大文件写耗费时间:" + (endTime - startTime) + "ms");
    }
}

v07 SXSSF大文件写.

// 07 版 SXSSF 大文件写demo (比XSSF占用内存少,写入更快)
public class ExcelWrite {
    private final String V07_SXSSF_BIGFILE = "v07_sxssf_bigfile_out.xlsx";
    private String resourcePath = System.getProperty("user.dir") + "\\src\\main\\resources\\";

    @Test
    public void testWrite() throws IOException {
        long startTime = System.currentTimeMillis();

        // 1.获取工作簿
        SXSSFWorkbook workbook = new SXSSFWorkbook();

        // 2.创建工作表
        SXSSFSheet sxssf = workbook.createSheet("sxssf");

        // 3.创建行 (65537)
        for (int i = 0; i < 65537; i++) {
            SXSSFRow row = sxssf.createRow(i);
            for (int j = 0; j < 5; j++) {
                // 4.创建单元格
                SXSSFCell cell = row.createCell(j);
                cell.setCellValue(""+i+j);
            }
        }

        // 文件输出流
        FileOutputStream fos = new FileOutputStream(new File(resourcePath+V07_SXSSF_BIGFILE));

        // 5.数据写出
        workbook.write(fos);

        // 关闭流
        fos.close();

        // 6.清理临时文件
        workbook.dispose();

        long endTime = System.currentTimeMillis();
        System.out.println("sxssf大文件写耗费时间:" + (endTime - startTime) + "ms");
    }
}

POI-Excel读.

v03.

public class ExcelRead {
    private final String fileName = "v03_hssf_bigfile_out.xls";

    @Test
    public void testRead() throws IOException {
        // 输入流
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(fileName);

        // 1.获取工作簿(输入流)
        HSSFWorkbook workbook = new HSSFWorkbook(is);

        // 2.获取工作表对象
        HSSFSheet hssf = workbook.getSheet("hssf");// ①按名字
        workbook.getSheetAt(0); // ②按索引
        workbook.sheetIterator(); // ③索引迭代器
        workbook.getNumberOfSheets(); // 获取索引数

        // 3.获取行对象
        HSSFRow row = hssf.getRow(0);// ①按索引
        hssf.getTopRow(); // ②获取顶行
        hssf.rowIterator(); // ③行迭代器

        // 4.获取单元格对象
        HSSFCell cell = row.getCell(0);
        row.cellIterator(); // 单元格迭代器
        row.getPhysicalNumberOfCells();// 行单元格数        

        // 5.输出单元内容
        String stringCellValue = cell.getStringCellValue(); // 字符串
        /*
        boolean booleanCellValue = cell.getBooleanCellValue(); // 布尔
        Date dateCellValue = cell.getDateCellValue(); // 日期
        byte errorCellValue = cell.getErrorCellValue(); // 字节(错误码)
        double numericCellValue = cell.getNumericCellValue(); // 浮点or整型
        HSSFRichTextString richStringCellValue = cell.getRichStringCellValue(); // 富文本(除文本,标点外,还可以包含其他元素)
        LocalDateTime localDateTimeCellValue = cell.getLocalDateTimeCellValue(); // 本地时间
        */

        // 6.打印单元格内容
        System.out.println(stringCellValue);

        // 关闭流
        is.close();
    }
}

v07.

public class ExcelRead {
    private final String fileName = "v07_xssf_bigfile_out.xlsx";

    @Test
    public void testRead() throws IOException {
        // 输入流
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(fileName);

        // 1.获取工作簿(输入流)
        XSSFWorkbook workbook = new XSSFWorkbook(is);

        // 2.获取工作表对象
        XSSFSheet xssf = workbook.getSheet("xssf");// ①按名字
        workbook.getSheetAt(0); // ②按索引
        workbook.sheetIterator(); // ③索引迭代器
        workbook.getNumberOfSheets(); // 获取索引数

        // 3.获取行对象
        XSSFRow row = xssf.getRow(0);// ①按索引
        xssf.getTopRow(); // ②获取顶行
        xssf.rowIterator(); // ③行迭代器

        // 4.获取单元格对象
        XSSFCell cell = row.getCell(0);
        row.cellIterator(); // 单元格迭代器
        row.getPhysicalNumberOfCells();// 行单元格数

        // 5.输出单元内容
        String stringCellValue = cell.getStringCellValue(); // 字符串
        /*
        boolean booleanCellValue = cell.getBooleanCellValue(); // 布尔
        Date dateCellValue = cell.getDateCellValue(); // 日期
        byte errorCellValue = cell.getErrorCellValue(); // 字节(错误码)
        double numericCellValue = cell.getNumericCellValue(); // 浮点or整型
        HSSFRichTextString richStringCellValue = cell.getRichStringCellValue(); // 富文本(除文本,标点外,还可以包含其他元素)
        LocalDateTime localDateTimeCellValue = cell.getLocalDateTimeCellValue(); // 本地时间
        */

        // 6.打印单元格内容
        System.out.println(stringCellValue);

        // 关闭流
        is.close();
    }
}

简单读取Demo.

类型判断、计算公式

计算公式步骤:(前提,cellType=FORMULA).

①获取公式计算器

 FormulaEvaluator formulaEvaluator = new XSSFFormulaEvaluator(workbook);

②得到公式名

 String cellFormula = cell.getCellFormula();

③进行公式计算

CellValue evaluate = formulaEvaluator.evaluate(cell);

④获取计算结果(字符串)

cellValue = evaluate.formatAsString();
读取Excel的表内容.
数据表.

实现源码.
public class Read {
    private static String fileName = "readDemo.xlsx";

    public static void main(String[] args) throws IOException {
        // 输入流
        InputStream is = Read.class.getClassLoader().getResourceAsStream(fileName);

        // 1.工作簿
        XSSFWorkbook workbook = new XSSFWorkbook(is);

        // 2.获取工作表
        XSSFSheet demo = workbook.getSheet("demo");

        // 3.获取第一行(内容的标题行)
        XSSFRow titleRow = demo.getRow(0);
        Iterator<Cell> titles = titleRow.cellIterator();
        while (titles.hasNext()) {
            Cell title = titles.next();
            System.out.print(title.getStringCellValue()+"\t\t");
        }


        // 3.获取其他行(内容的记录行)
        int recordNum = demo.getPhysicalNumberOfRows();
        for (int i = 1; i < recordNum; i++) {
            System.out.println(); // 换行
            XSSFRow record = demo.getRow(i);
            Iterator<Cell> data = record.cellIterator();
            while (data.hasNext()) {
                Cell cell = data.next();
                String cellValue = "";
                switch (cell.getCellType()){ // 判断单元格类型,根据不同类型进行打印操作
                    case BLANK: // 空单元格 == ""
                        break;
                    case BOOLEAN: // 布尔
                        cellValue = String.valueOf(cell.getBooleanCellValue());
                        break;
                    case ERROR: // 错误
                        cellValue = String.valueOf(cell.getErrorCellValue());
                        break;
                    case NUMERIC: // 数值
                        //HSSFDateUtil.isCellDateFormatted(cell); // @deprecated
                        if (DateUtil.isCellDateFormatted(cell)) { // 日期格式
                            cellValue = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(cell.getDateCellValue());
                        } else { // 非日期的数值类型
                            cellValue = String.valueOf(cell.getNumericCellValue());
                        }
                        break;
                    case STRING: // 字符串
                        cellValue = String.valueOf(cell.getStringCellValue());
                        break;
                    case FORMULA: // 公式
                        // 1.公式计算器
                        FormulaEvaluator formulaEvaluator = new XSSFFormulaEvaluator(workbook);
                        // 2.得到公式
                        String cellFormula = cell.getCellFormula();
                        System.out.print(cellFormula);
                        // 3.计算公式
                        CellValue evaluate = formulaEvaluator.evaluate(cell);
                        // 4.计算的结果
                        cellValue = evaluate.formatAsString();
                        break;
                    default:
                        throw new RuntimeException("不知道的类型:" + cell.getCellType());
                }
                //打印单元格
                System.out.print(cellValue+"\t\t");
            }

        }

        // 关闭流
        is.close();
    }
}
结果展示.

easyExcel.

官方文档:https://www.yuque.com/easyexcel/doc/quickstart

Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到几M,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便

相较于POI,更省内存,使用更方便

准备工作:.

看实际使用情况,准备

普通maven项目.

非web项目,或者 Jsp+servlet的web项目

导入依赖.
<!-- easy-excel -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.2.7</version>
</dependency>
<!-- lombok 需要安装插件 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.16</version>
</dependency>
<!-- test -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13</version>
</dependency>
<!-- fastjson -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.70</version>
</dependency>
<!-- jul(java.util.logging) -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jdk14</artifactId>
    <version>1.7.25</version>
</dependency>
数据库创建.
create database if not exists `poi`;

use poi;

create table `demo`(
    `string` varchar(20),
    `date` datetime default current_timestamp,
    `double_data` double
)
编写db.properties.

文件存放在 resources目录下

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/poi?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username=root
password=mysql
编写 ConnectionUtil.
public class ConnectionUtil {
    private static Connection conn = null;
    private static String driver = null;
    private static String url = null;
    private static String password = null;
    private static String username = null;

    static {
        InputStream is = ConnectionUtil.class.getClassLoader().getResourceAsStream("db.properties");
        Properties properties = new Properties();
        try {
            properties.load(is);
            driver = properties.getProperty("driver");
            url = properties.getProperty("url");
            password = properties.getProperty("password");
            username = properties.getProperty("username");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static Connection getConn(){
        try {
            Class.forName(driver); // 加载驱动类
            conn = DriverManager.getConnection(url,username,password);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return conn;
    }

    public static void closeConn(Connection connection){
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}
编写BaseDao.
public class BaseDao {

    protected void query(Connection connection, String sql, Object... params){
        PreparedStatement ps = null;
        ResultSet rs = null;
        if (connection != null) {
            try {
                ps = connection.prepareStatement(sql); // 预编译sql
                // 设置占位符对应的值
                if (params != null) {
                    for (int i = 1; i <= params.length; i++) {
                        ps.setObject(i,params[i-1]);
                    }
                }
                rs = ps.executeQuery(); // 执行查询
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            } finally {
                if (ps != null) {
                    try {
                        // 关闭连接 释放资源
                        ps.close();
                        connection.close();
                    } catch (SQLException throwables) {
                        throwables.printStackTrace();
                    }
                }
            }
        }
    }

    protected void execute(Connection connection,String sql,Object... params){
        PreparedStatement ps = null;

        if (connection != null) {
            try {
                ps = connection.prepareStatement(sql);
                // 设置占位符对应的值
                if (params != null) {
                    for (int i = 1; i <= params.length; i++) {
                        ps.setObject(i, params[i-1]);
                    }
                }
                if (ps.executeUpdate()>0) { // 执行更新操作(insert、update、delete)
                    System.out.println("操作成功");
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            } finally {
                if (ps != null) {
                    try {
                        ps.close();
                        connection.close();
                    } catch (SQLException throwables) {
                        throwables.printStackTrace();
                    }
                }
            }
        }
    }
}
数据准备.

read.xls文件存放在 resources目录下

字符串 日期 浮点数
字符串xx 2021/2/14 1.3

整合springboot.

创建springboot项目

数据准备.

read-demo.xlsx 文件存放在 resources目录下

id age name version deleted create_time update_time 公式
1 18 张三 1 0 2021/1/3 0:00 2021/1/3 0:00 =PI()
2 19 李四 1 0 2021/1/3 0:00 2021/1/3 0:00
3 20 王五 1 0 2021/1/3 0:00 2021/1/3 0:00

快速入门.

EasyExcel-读.

简单读.
  • 创建实体类
  • 创建监听器
  • EasyExcel.read()….读取Excel文件
DemoData实体类.
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DemoData {
    private String string;
    private Date date;
    private Double doubleData;
}
DemoDAO持久类.
public class DemoDAO extends BaseDao {
    public void save(List<DemoData> list) {
        Connection conn = ConnectionUtil.getConn();
        StringBuilder value = new StringBuilder();
        if (list != null) {
            for (DemoData demoData : list) {
                value.append("(");
                String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(demoData.getDate());
                value.append("'"+demoData.getString()+"','"+date+"',"+demoData.getDoubleData());
                value.append("),");
            }
        } else {
            throw new RuntimeException("list不能为空");
        }
        if (!StringUtils.isEmpty(value.toString())){
            execute(conn,"insert into demo(string,date,double_data) values"+value.substring(0, value.length() - 1));
        }
    }
}
DemoDataListener监听器(官方).
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
class DemoDataListener extends AnalysisEventListener<DemoData> {
    private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class);
    /**
     * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;
    List<DemoData> list = new ArrayList<DemoData>();
    /**
     * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
     */
    private DemoDAO demoDAO;

    public DemoDataListener() {
        // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
        demoDAO = new DemoDAO();
    }
    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     *
     * @param demoDAO
     */
    public DemoDataListener(DemoDAO demoDAO) {
        this.demoDAO = demoDAO;
    }
    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data
     *            one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(DemoData data, AnalysisContext context) {
        LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));
        list.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (list.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            list.clear();
        }
    }
    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        LOGGER.info("所有数据解析完成!");
    }
    /**
     * 加上存储数据库
     */
    private void saveData() {
        LOGGER.info("{}条数据,开始存储数据库!", list.size());
        demoDAO.save(list);
        LOGGER.info("存储数据库成功!");
    }
}
测试.
public class TestRead {
    String fileName = System.getProperty("user.dir") + "\\src\\main\\resources\\read.xls";
    InputStream is = TestRead.class.getClassLoader().getResourceAsStream("read.xls");

    // 简单读 (写法一)
    @Test
    public void simpleRead_1() {
        // 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
        EasyExcel.read(is, DemoData.class, new DemoDataListener()).sheet(0).doRead();
    }

    // 简单读 (写法二)
    @Test
    public void simpleRead_2() {
        ExcelReader excelReader = null;
        try {
            excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build();
            ReadSheet readSheet = EasyExcel.readSheet(0).build();
            excelReader.read(readSheet);
        } finally {
            // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
            if (excelReader != null) {
                excelReader.finish();
            }
        }
    }
}
结果展示.

指定列的下标或者列名.
涉及注解:@ExcelProperty.
public @interface ExcelProperty {
    // 标题名
    String[] value() default {""};

    // 索引
    int index() default -1;

    // 排序
    int order() default Integer.MAX_VALUE;

    // 格式转换
    Class<? extends Converter> converter() default AutoConverter.class;

    // use {@link com.alibaba.excel.annotation.format.DateTimeFormat}
    @Deprecated
    String format() default "";
}

修改实体类,为字段添加注解指定下标或名字

@Data
@AllArgsConstructor
@NoArgsConstructor
public class DemoData {
    @ExcelProperty("字符串")
    private String string;
    @ExcelProperty("浮点数")
    private Double doubleData;
    @ExcelProperty(index = 1)
    private Date date;
}
读多个sheet.
// 读取全部
// DemoDataListener的doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。然后所有sheet都会往同一个DemoDataListener里面写
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).doReadAll();
// 读取部分
fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
ExcelReader excelReader = null;
try {
    excelReader = EasyExcel.read(fileName).build();

    // 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener
    ReadSheet readSheet1 =
        EasyExcel.readSheet(0).head(DemoData.class).registerReadListener(new DemoDataListener()).build();
    ReadSheet readSheet2 =
        EasyExcel.readSheet(1).head(DemoData.class).registerReadListener(new DemoDataListener()).build();
    // 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能
    excelReader.read(readSheet1, readSheet2);
} finally {
    if (excelReader != null) {
        // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
        excelReader.finish();
    }
}
日期、数字或自定义格式转换.
@Data
public class ConverterData {
    /**
     * 我自定义 转换器,不管数据库传过来什么 。我给他加上“自定义:”
     */
    @ExcelProperty(converter = CustomStringStringConverter.class)
    private String string;
    /**
     * 这里用string 去接日期才能格式化。我想接收年月日格式
     */
    @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
    private String date;
    /**
     * 我想接收百分比的数字
     */
    @NumberFormat("#.##%")
    private String doubleData;
}
转换器.
public class CustomStringStringConverter implements Converter<String> {
    @Override
    public Class supportJavaTypeKey() {
        return String.class;
    }
    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }
    /**
     * 这里读的时候会调用
     *
     * @param cellData
     *            NotNull
     * @param contentProperty
     *            Nullable
     * @param globalConfiguration
     *            NotNull
     * @return
     */
    @Override
    public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
        GlobalConfiguration globalConfiguration) {
        return "自定义:" + cellData.getStringValue();
    }
    /**
     * 这里是写的时候会调用 不用管
     *
     * @param value
     *            NotNull
     * @param contentProperty
     *            Nullable
     * @param globalConfiguration
     *            NotNull
     * @return
     */
    @Override
    public CellData convertToExcelData(String value, ExcelContentProperty contentProperty,
        GlobalConfiguration globalConfiguration) {
        return new CellData(value);
    }
}
@Test
public void converterRead() {
    String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
    // 这里 需要指定读用哪个class去读,然后读取第一个sheet 
    EasyExcel.read(fileName, ConverterData.class, new ConverterDataListener())
        // 这里注意 我们也可以registerConverter来指定自定义转换器, 但是这个转换变成全局了, 所有java为string,excel为string的都会用这个转换器。
        // 如果就想单个字段使用请使用@ExcelProperty 指定converter
        // .registerConverter(new CustomStringStringConverter())
        // 读取sheet
        .sheet().doRead();
}

EasyExcel-写.

EasyExcel-填充.

扩展:.

POI SXSSF使用注意.

https://blog.csdn.net/lipinganq/article/details/53434884

  1. SXSSF通过限制对滑动窗口中的行的访问来实现其低内存占用
  2. 默认窗口大小windowSize为100,由SXSSFWorkbook.DEFAULT_WINDOW_SIZE定义。
  3. 可以通过新的SXSSFWorkbook(int windowSize)在工作簿构建时指定窗口大小
    SXSSFWorkbook wb1 = new SXSSFWorkbook(100);
  4. 也可以通过SXSSFSheet#setRandomAccessWindowSize(int windowSize)
  5. windowSize为-1表示无限制访问。在这种情况下,所有尚未通过调用flushRows()刷新的记录可用于随机访问。
  6. 当通过createRow()创建一个新行并且未刷新记录的总数超过指定的窗口大小时,具有最低索引值的行将被刷新,并且不能再通过getRow()访问。
    比如窗口行数为100,内存当前有100行,createRow()创建一个新行,索引值为0的那一行被刷新到本地文件,该行将无法访问,因为它们已写入磁盘。
  7. SXSSF分配临时文件,您必须始终清除显式,通过调用dispose方法
    SXSSFWorkbook wb2 = new SXSSFWorkbook(100);
    ……
    wb2.dispose();
  8. SXSSFWorkbook默认使用内联字符串而不是共享字符串表(SharedStringsTable)。这是非常有效的,因为没有文档内容需要保存在存储器中,但是也已知生成与一些客户端不兼容的文档。
  9. 启用共享字符串时,文档中的所有唯一字符串都必须保存在内存中。根据文档内容,这可能使用比禁用共享字符串更多的资源。
  10. 在决定是否启用共享字符串之前,仔细查看内存预算和兼容性需求
/**
 * workbook - 模板工作簿
 * rowAccessWindowSize - 保存在内存中,直到刷新的行数。
 * compressTmpFiles - 是否对临时文件使用gzip压缩
 * useSharedStringsTable - 是否使用共享字符串表
 */
SXSSFWorkbook(XSSFWorkbook workbook, int rowAccessWindowSize, boolean compressTmpFiles, boolean useSharedStringsTable)1234567
  1. 根据使用的功能,仍然有可能会消耗大量内存的内容,例如合并区域,超链接,注释,…仍然仅存储在存储器中,因此如果广泛使用可能需要大量存储器。

文章作者: liuminkai
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 liuminkai !
评论
 上一篇
日期注解转换 日期注解转换
问题说明. 从数据库获取时间传到前端进行展示的时候,我们有时候可能无法得到一个满意的时间格式的时间日期,在数据库中显示的是正确的时间格式,获取出来却变成了很丑的时间戳。(数据库==>前端) @JsonFormat注解很好的解决了这个
2021-01-06
下一篇 
MyBatisPlus学习笔记 MyBatisPlus学习笔记
MyBatisPlus 3.4.1,含低版本部分配置; 一、MyBatisPlus简介. 官网:https://baomidou.com/ MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis
2020-12-31
  目录