一、概述
目前操作 Excel 比较流行的就是 Apache POI 和阿里巴巴的 easyExcel。
1.1 POI 简介
Apache POI 是用 Java 编写的免费开源的跨平台的 Java API,Apache POI 提供 API 给 Java 程序对 Microsoft Office 格式文档读和写的常用功能。POI 为 “Poor Obfuscation Implementation” 的首字母缩写,意为“简洁版的模糊实现”。其常用的结构如下:
HSSF -- 提供读写 03 版本的 Excel 常用功能。
XSSF -- 提供读写 07 版本的 Excel 常用功能。
HWPF -- 提供读写 Word 格式的常用功能
HSLF -- 提供读写 ppt 格式的常用功能。
HDGF -- 提供读写 visio 格式的常用功能
1.2 easyExcel 简介
easyExcel 是阿里巴巴开源的一个 excel 处理框架,以使用简单、节省内存著称。easyExcel 能大大减少占用内存的主要原因是在解析 Excel 时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
easyExcel 官网地址:https://github.com/alibaba/easyexcel
1.3 xls 和 xlsx 区别
常用的 excel 文档有两种结尾形式,分别为 xls 和 xlsx,其中以 xls 结尾的文档属于 03 版本的,它里面最多可以存储 65536 行数据。而以 xlsx 结尾的文档属于 07 版本的,它理论上可以存储无限行数据,这就是两者之前的区别。
二、POI 常用操作
2.1 添加 maven 依赖
    <dependencies>
        <!--xls(03 版本)-->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.9</version>
        </dependency>
        <!--xlsx(07 版本)-->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.9</version>
        </dependency>
        <!-- 日期格式化工具-->
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.10.1</version>
        </dependency>
        <!--test-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>2.2 写入 Excel 操作
2.2.1 一般文件写入
2.2.1.1 03 版本
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
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 org.joda.time.DateTime;
import org.junit.Test;
import java.io.FileOutputStream;
public class ExcelWriteTest {
    String PATH ="F:\\idea_home\\poi-excel\\";
    @Test
    public void testWrite03() throws Exception {
        // 1、创建一个工作簿
        Workbook workbook = new HSSFWorkbook();
        // 2、创建一个工作表
        Sheet sheet = workbook.createSheet("我是 sheet1 页");
        // 3、创建一行
        Row row1  = sheet.createRow(0);
        // 4、创建一个单元格
        Cell cell11 = row1.createCell(0);
        cell11.setCellValue("我是第一行第一个单元格");
        Cell cell12 = row1.createCell(1);
        cell12.setCellValue("我是第一行第二个单元格");
        // 第二行
        Row row2  = sheet.createRow(1);
        Cell cell21 = row2.createCell(0);
        cell21.setCellValue("我是第二行第一个单元格");
        Cell cell22 = row2.createCell(1);
        String time = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
        cell22.setCellValue(time);
        // 03 版本的使用 xls 结尾
        FileOutputStream fileOutputStream  = new FileOutputStream(PATH+"统计表03类型.xls");
        workbook.write(fileOutputStream);
        fileOutputStream.close();
        System.out.println("Excel03 写入完成了");
    }
}
2.2.2.2 07 版本
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 org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.joda.time.DateTime;
import org.junit.Test;
import java.io.FileOutputStream;
public class ExcelWriteTest {
    String PATH ="F:\\idea_home\\poi-excel\\";
    @Test
    public void testWrite07() throws Exception {
        // 1、创建一个工作簿
        Workbook workbook = new XSSFWorkbook();
        // 2、创建一个工作表
        Sheet sheet = workbook.createSheet("我是 sheet1 页");
        // 3、创建一行
        Row row1  = sheet.createRow(0);
        // 4、创建一个单元格
        Cell cell11 = row1.createCell(0);
        cell11.setCellValue("我是第一行第一个单元格");
        Cell cell12 = row1.createCell(1);
        cell12.setCellValue("我是第一行第二个单元格");
        // 第二行
        Row row2  = sheet.createRow(1);
        Cell cell21 = row2.createCell(0);
        cell21.setCellValue("我是第二行第一个单元格");
        Cell cell22 = row2.createCell(1);
        String time = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
        cell22.setCellValue(time);
        // 07 版本的使用 xlsx 结尾
        FileOutputStream fileOutputStream  = new FileOutputStream(PATH+"统计表07类型.xlsx");
        workbook.write(fileOutputStream);
        fileOutputStream.close();
        System.out.println("Excel07 写入完成了");
    }
}
2.2.2 大文件写入
2.2.2.1 03 版本
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
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 org.junit.Test;
import java.io.FileOutputStream;
public class ExcelWriteTest {
    String PATH ="F:\\idea_home\\poi-excel\\";
    @Test
    public void testWrite03BigData() throws Exception {
        long begin = System.currentTimeMillis();
        // 1、创建一个工作簿
        Workbook workbook = new HSSFWorkbook();
        // 2、创建一个工作表
        Sheet sheet = workbook.createSheet();
        // 3、写入数据
        for(int rowNum =0;rowNum<65537;rowNum++){
            Row row = sheet.createRow(rowNum);
            for(int cellNum=0;cellNum<10;cellNum++){
                Cell cell = row.createCell(cellNum);
                cell.setCellValue(cellNum);
            }
        }
        System.out.println("over");
        FileOutputStream fileOutputStream  = new FileOutputStream(PATH+"统计表03大数据类型.xls");
        workbook.write(fileOutputStream);
        fileOutputStream.close();
        long end = System.currentTimeMillis();
        System.out.println((double)(end-begin)/1000);
    }
}缺点:最多只能处理 65536 行,否则会抛出异常。

优点:写入过程中写入缓存,不操作磁盘,最后一次性写入磁盘,速度快。将 65537 改成65536 再次执行程序,结果如下,可以看到 1.692s 就完成了写入操作,速度还是很快的。

2.2.2.2 07 版本
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 org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.Test;
import java.io.FileOutputStream;
public class ExcelWriteTest {
    String PATH ="F:\\idea_home\\poi-excel\\";
    
    @Test
    public void testWrite07BigData() throws Exception {
        long begin = System.currentTimeMillis();
        // 1、创建一个工作簿
        Workbook workbook = new XSSFWorkbook();
        // 2、创建一个工作表
        Sheet sheet = workbook.createSheet();
        // 3、写入数据
        for(int rowNum =0;rowNum<100000;rowNum++){
            Row row = sheet.createRow(rowNum);
            for(int cellNum=0;cellNum<10;cellNum++){
                Cell cell = row.createCell(cellNum);
                cell.setCellValue(cellNum);
            }
        }
        System.out.println("over");
        FileOutputStream fileOutputStream  = new FileOutputStream(PATH+"统计表07大数据类型.xlsx");
        workbook.write(fileOutputStream);
        fileOutputStream.close();
        long end = System.currentTimeMillis();
        System.out.println((double)(end-begin)/1000);
    }
}缺点:写数据时速度非常慢,非常耗内存,也会发生内存溢出,如100万条。
优点:可以写较大的数据量,如20万条。

2.2.2.3 07 版本优化
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 org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.junit.Test;
import java.io.FileOutputStream;
public class ExcelWriteTest {
    String PATH ="F:\\idea_home\\poi-excel\\";
    @Test
    public void testWrite07BigDataS() throws Exception {
        long begin = System.currentTimeMillis();
        // 1、创建一个工作簿
        Workbook workbook = new SXSSFWorkbook();
        // 2、创建一个工作表
        Sheet sheet = workbook.createSheet();
        // 3、写入数据
        for(int rowNum =0;rowNum<100000;rowNum++){
            Row row = sheet.createRow(rowNum);
            for(int cellNum=0;cellNum<10;cellNum++){
                Cell cell = row.createCell(cellNum);
                cell.setCellValue(cellNum);
            }
        }
        System.out.println("over");
        FileOutputStream fileOutputStream  = new FileOutputStream(PATH+"统计表07大数据类型优化.xlsx");
        workbook.write(fileOutputStream);
        // 清除产生的临时文件
        ((SXSSFWorkbook)workbook).dispose();
        fileOutputStream.close();
        long end = System.currentTimeMillis();
        System.out.println((double)(end-begin)/1000);
    }
}优点:可以写非常大的数据量,如 100万 条甚至更多条,数据速度快,占用更少的内存。

需要注意的是:代码在过程中会产生临时文件,需要清理临时文件。默认有 100 条记录被保存在内存中,如果超过这数量,则最前面的数据被写入临时文件。如果想自定义内存中数据的数量,可以使用 new SXSSFWorkbook(数量) 。
SXSSFWorkbook 来至官方的解释:实现 “BigGridDemo” 策略的流式 XSSFWorkbook 版本。这允许写入非常大的文件而不会耗尽内存,因为任何时候只有可配置的行部分被保存在内存中。请注意,仍然可能会消耗大量内存,这些内存基于您正在使用的功能,例如合并区域,注释……仍然只存储在内存中,因此如果广泛使用,可能需要大量内存。
2.3 读取 Excel 操作
2.3.1 03 版本
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.junit.Test;
import java.io.FileInputStream;
public class ExcelRead {
    String PATH ="F:\\idea_home\\poi-excel\\";
    @Test
    public void testRead03() throws Exception {
        // 1、获取文件流
        FileInputStream fileInputStream = new FileInputStream(PATH+"统计表03类型.xls");
        // 2、创建文件簿,使用 excel 能操作的这边都可以操作
        Workbook workbook = new HSSFWorkbook(fileInputStream);
        // 3、得到表
        Sheet sheet = workbook.getSheetAt(0);
        // 4、得到行
        Row row  = sheet.getRow(0);
        // 5、得到列
        Cell cell = row.getCell(0);
        // 读取值的时候需要注意类型,String 和数字调用的方法是不同的。
        System.out.println(cell.getStringCellValue());
        fileInputStream.close();
    }
}
2.3.2 07 版本
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.Test;
import java.io.FileInputStream;
public class ExcelRead {
    String PATH ="F:\\idea_home\\poi-excel\\";
    @Test
    public void testRead07() throws Exception {
        // 1、获取文件流
        FileInputStream fileInputStream = new FileInputStream(PATH+"统计表07类型.xlsx");
        // 2、创建文件簿,使用 excel 能操作的这边都可以操作
        Workbook workbook = new XSSFWorkbook(fileInputStream);
        // 3、得到表
        Sheet sheet = workbook.getSheetAt(0);
        // 4、得到行
        Row row  = sheet.getRow(0);
        // 5、得到列
        Cell cell = row.getCell(1);
        // 读取值的时候需要注意类型,String 和数字调用的方法是不同的。
        System.out.println(cell.getStringCellValue());
        fileInputStream.close();
    }
}
2.3.3 读取不同类型
表格的内容如下所示:

代码如下所示:
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.joda.time.DateTime;
import org.junit.Test;
import java.io.FileInputStream;
import java.util.Date;
public class ExcelRead {
    String PATH ="F:\\idea_home\\poi-excel\\";
    @Test
    public void testCellType() throws Exception{
        // 1、获取文件流
        FileInputStream fileInputStream = new FileInputStream(PATH+"03商品信息.xls");
        // 2、创建文件簿,使用 excel 能操作的这边都可以操作
        Workbook workbook = new HSSFWorkbook(fileInputStream);
        // 3、得到表
        Sheet sheet = workbook.getSheetAt(0);
        // 4、获取标题内容
        Row rowTitle  = sheet.getRow(0);
        if(rowTitle != null){
            // 获取列的数量
            int cellCount = rowTitle.getPhysicalNumberOfCells();
            for(int cellNum=0;cellNum<cellCount;cellNum++){
                Cell cell = rowTitle.getCell(cellNum);
                if(cell != null){
                    // 获取列的类型
                    int cellType = cell.getCellType();
                    // 获取具体的列名
                    String cellValue = cell.getStringCellValue();
                    System.out.print(cellValue+" | ");
                }
            }
        }
        System.out.println();
        // 5、获取表中的内容
        // 获取有多少行的记录
        int rowCount = sheet.getPhysicalNumberOfRows();
        for(int rowNum=1;rowNum<rowCount;rowNum++){
            // 获取第一行数据
            Row rowData = sheet.getRow(rowNum);
            if(rowData !=null){
                // 读取行中的列
                int cellCount = rowTitle.getPhysicalNumberOfCells();
                for(int cellNum=0;cellNum<cellCount;cellNum++){
                    System.out.print("["+(rowNum+1)+"-"+(cellNum+1)+"]");
                    Cell cell = rowData.getCell(cellNum);
                    // 匹配类的数据类型
                    if(cell != null){
                        int cellType = cell.getCellType();
                        String cellValue="";
                        switch(cellType){
                            case HSSFCell.CELL_TYPE_STRING:   //字符串
                                System.out.print("【STRING】");
                                cellValue = cell.getStringCellValue();
                                break;
                            case HSSFCell.CELL_TYPE_BOOLEAN:   //布尔
                                System.out.print("【BOOLEAN】");
                                cellValue = String.valueOf(cell.getBooleanCellValue());
                                break;
                            case HSSFCell.CELL_TYPE_BLANK:   //空
                                System.out.print("【BLANK】");
                                break;
                            case HSSFCell.CELL_TYPE_NUMERIC:   //数字(分为日期和普通数字)
                                System.out.print("【NUMERIC】");
                                if(HSSFDateUtil.isCellDateFormatted(cell)){ // 日期
                                    System.out.print("【日期】");
                                    Date date = cell.getDateCellValue();
                                    cellValue = new DateTime(date).toString("yyyy-MM-dd");
                                }else{
                                    // 非日期格式,转换成字符串格式
                                    System.out.print("【转化为字符串输出】");
                                    cell.setCellType(HSSFCell.CELL_TYPE_STRING);
                                    cellValue = cell.toString();
                                }
                                break;
                            case HSSFCell.CELL_TYPE_ERROR:   //字符串
                                System.out.print("【数据类型错误】");
                                break;
                        }
                        System.out.println(cellValue);
                    }
                }
            }
        }
        fileInputStream.close();
    }
}
2.3.4 读取公式
操作的表格内容如下所示:

代码如下所示:
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.junit.Test;
import java.io.FileInputStream;
public class ExcelRead {
    String PATH ="F:\\idea_home\\poi-excel\\";
    @Test
    public void testFormula() throws Exception {
        FileInputStream fileInputStream = new FileInputStream(PATH + "03求和.xls");
        // 1.创建一个工作簿。使得excel能操作的,这边他也能操作。
        Workbook workbook = new HSSFWorkbook(fileInputStream);
        // 2.得到表。
        Sheet sheet = workbook.getSheetAt(0);
        Row row = sheet.getRow(6);
        Cell cell = row.getCell(0);
        // 拿到计算公司
        FormulaEvaluator formulaEvaluator = new HSSFFormulaEvaluator((HSSFWorkbook) workbook);
        // 输出单元格内容
        int cellType = cell.getCellType();
        switch (cellType){
            case Cell.CELL_TYPE_FORMULA:
                String cellFormula = cell.getCellFormula();
                System.out.println(cellFormula);
                // 计算
                CellValue evaluate = formulaEvaluator.evaluate(cell);
                String cellValue = evaluate.formatAsString();
                System.out.println(cellValue);
                break;
        }
    }
}
三、EasyExcel 常用操作
3.1 添加 maven 依赖
    <dependencies>
        <!-- 主要是这个依赖,剩下的依赖都是测试用到的 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>3.0.5</version>
        </dependency>
        <dependency>
            <groupId>lambada</groupId>
            <artifactId>lambada</artifactId>
            <version>1.0.3</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <!--test-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.7</version>
        </dependency>
    </dependencies>3.2 写操作
先模拟一个实体类,如下所示:
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
@Getter
@Setter
@EqualsAndHashCode
public class DemoData {
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ExcelProperty("数字标题")
    private Double doubleData;
    /**
     * 忽略这个字段
     */
    @ExcelIgnore
    private String ignore;
}然后写入文档即可,如下所示:
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.util.ListUtils;
import org.junit.Test;
import java.util.Date;
import java.util.List;
public class TestWrite {
    @Test
    public void simpleWrite() {
        String PATH ="F:\\idea_home\\poi-excel\\easyexcel统计表03类型.xlsx";
        EasyExcel.write(PATH, DemoData.class)
                .sheet("模板")
                .doWrite(() -> {
                    // 分页查询数据
                    return data();
                });
    }
    private List<DemoData> data() {
        List<DemoData> list = ListUtils.newArrayList();
        for (int i = 0; i < 10; i++) {
            DemoData data = new DemoData();
            data.setString("字符串" + i);
            data.setDate(new Date());
            data.setDoubleData(0.56);
            list.add(data);
        }
        return list;
    }
}
3.3 读操作
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
@Getter
@Setter
@EqualsAndHashCode
public class DemoData {
    private String string;
    private Date date;
    private Double doubleData;
}import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.listener.PageReadListener;
import com.alibaba.fastjson.JSON;
import org.junit.Test;
public class TestRead {
    @Test
    public void simpleRead() {
        String PATH ="F:\\idea_home\\poi-excel\\easyexcel统计表03类型.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
        // 这里每次会读取3000条数据 然后返回过来 直接调用使用数据就行
        EasyExcel.read(PATH, DemoData.class, new PageReadListener<DemoData>(dataList -> {
            for (DemoData demoData : dataList) {
                System.out.println(JSON.toJSONString(demoData));
            }
        })).sheet().doRead();
    }
}
3.4 更多操作
详细的文档地址:https://www.yuque.com/easyexcel/doc/easyexcel



















