Excel - POI与EasyExcel操作Excel表

POI

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

  1. HSSF - 提供读写Microsoft Excel格式档案的功能。
  2. XSSF - 提供读写Microsoft Excel OOXML格式档案的功能。
  3. HWPF - 提供读写Microsoft Word格式档案的功能。
  4. HSLF - 提供读写Microsoft PowerPoint格式档案的功能。
  5. HDGF - 提供读写Microsoft Visio格式档案的功能。
Excel 03版本和07版本

03版本的Excel,即后缀名为.xls的。07版本的Excel,即后缀名为.xlsx的。
Excel03:只支持xls类型的文档,最多65536行、256列。
Excel07:除了支来持xls类型文档,还支持xlsx类型的文档,最多1048576行、6384列。

Excel对象

工作簿:一个Excel文件
工作表:一个Excel文件中的表
行、列:每一行、每一列
Excel - POI与EasyExcel操作Excel表_第1张图片

POI操作Excel
引入相关依赖
<!-- xls(Excel 03版本) -->
<dependency>
	<groupId>org.apache.poi</groupId>
	<artifactId>poi</artifactId>
	<version>3.9</version>
</dependency>
<!-- xlsx(Excel 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>
<!-- 单元测试 -->
<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
	<version>4.12</version>
</dependency>
基本写操作

1、03版本

public class ExcelWriteTest {
    // 文件输入位置
    public static String Path = "D:\\";

    @Test
    public void testWrite03() throws IOException {
        // 创建一个工作簿
        Workbook workbook = new HSSFWorkbook();
        // 根据工作簿创建一个工作表
        Sheet sheet = workbook.createSheet("柳成荫");
        // 根据工作表创建一行
        Row row0 = sheet.createRow(0);
        // 根据行创建一个单元格
        Cell cell00 = row0.createCell(0);
        cell00.setCellValue("今日听歌数");   // 即Excel里的坐标(0,0),第一行第一列的格子

        Row row1 = sheet.createRow(1);
        Cell cell10 = row1.createCell(0);
        cell10.setCellValue(10);   // 即Excel里的坐标(1,0),第二行第一列的格子

        Cell cell01 = row0.createCell(1);
        cell01.setCellValue("统计时间");   // (0,1)

        Cell cell11 = row1.createCell(1);
        cell11.setCellValue(new DateTime().toString("yyyy-MM-dd HH:mm:ss"));  // (1,1)

        // 生成一张表
        OutputStream outputStream = new FileOutputStream(Path + "03版本测试.xls");
        workbook.write(outputStream);
        // 关闭流
        outputStream.close();
        System.out.println("03版本Excel生成完毕!");
    }
}

Excel - POI与EasyExcel操作Excel表_第2张图片
2、07版本
相对上面03版本,代码没有什么变化,就是创建工作簿的对象(XSSFWorkbook)变了和文件后缀(.xlsx)变了

@Test
public void testWrite07() throws IOException {
    // 创建一个工作簿 - 07
    Workbook workbook = new XSSFWorkbook();
    // 根据工作簿创建一个工作表
    Sheet sheet = workbook.createSheet("柳成荫");
    // 根据工作表创建一行
    Row row0 = sheet.createRow(0);
    // 根据行创建一个单元格
    Cell cell00 = row0.createCell(0);
    cell00.setCellValue("今日听歌数");   // 即Excel里的坐标(0,0),第一行第一列的格子

    Row row1 = sheet.createRow(1);
    Cell cell10 = row1.createCell(0);
    cell10.setCellValue(10);   // 即Excel里的坐标(1,0),第二行第一列的格子

    Cell cell01 = row0.createCell(1);
    cell01.setCellValue("统计时间");   // (0,1)

    Cell cell11 = row1.createCell(1);
    cell11.setCellValue(new DateTime().toString("yyyy-MM-dd HH:mm:ss"));  // (1,1)

    // 生成一张表
    OutputStream outputStream = new FileOutputStream(Path + "07版本测试.xlsx");
    workbook.write(outputStream);
    // 关闭流
    outputStream.close();
    System.out.println("07版本Excel生成完毕!");
}
大数据量的写入

1、03版本

@Test
public void testWrite03BigData() throws IOException{
    // 开始写入时间
    long begin = System.currentTimeMillis();
    // 创建工作簿
    Workbook workbook = new HSSFWorkbook();
    // 创建表
    Sheet sheet = workbook.createSheet("九月清晨");
    for (int rowNum = 0; rowNum < 65536; rowNum++){
        Row row = sheet.createRow(rowNum);
        for (int cellNum = 0; cellNum < 256; cellNum++){
            Cell cell = row.createCell(cellNum);
            cell.setCellValue(cellNum);
        }
    }
    OutputStream outputStream = new FileOutputStream(Path + "03bigData.xls");
    workbook.write(outputStream);
    outputStream.close();
    // 结束写入时间
    long end = System.currentTimeMillis();
    System.out.println("写入完毕,花费时间:");
    System.out.println((double) (end - begin)/1000);
}

在这里插入图片描述
2、07版本
在03版本基础上,只需修改对象(XSSFWorkbook)和文件后缀名(.xlsx)即可。
相对03版本,写入相同的数据花费时间更长,但是07能写入的数据更多。
3、07版本升级版 - SXSSFWorkbook
它可以写入非常大的数据量,如100万条甚至更多,写数据速度极快,占用内存更少。
但是它会在写入过程中产生临时文件,默认是100条数据被保存在内存中,如果超过这个数量,则最前面的数据被写入临时文件,如果想要自定义内存中数据的数量,可以使用SXSSFWorkbook(数量)

@Test
public void testWrite07BigDataPlus() throws IOException{
    // 开始写入时间
    long begin = System.currentTimeMillis();
    // 创建工作簿
    Workbook workbook = new SXSSFWorkbook();
    // 创建表
    Sheet sheet = workbook.createSheet("九月清晨");
    for (int rowNum = 0; rowNum < 65536; rowNum++){
        Row row = sheet.createRow(rowNum);
        for (int cellNum = 0; cellNum < 256; cellNum++){
            Cell cell = row.createCell(cellNum);
            cell.setCellValue(cellNum);
        }
    }
    OutputStream outputStream = new FileOutputStream(Path + "07bigDataPlus.xlsx");
    workbook.write(outputStream);
    outputStream.close();
    ((SXSSFWorkbook) workbook).dispose();   // 删除生成的临时文件
    // 结束写入时间
    long end = System.currentTimeMillis();
    System.out.println("写入完毕,花费时间:");
    System.out.println((double) (end - begin)/1000);
}

我这个电脑有点捞,花费时间比较长,上面那个低配版本写了很久都没写完所以才没截图的。
在这里插入图片描述

基本读操作

读操作,需要根据类型来读,也就是需要判断类型。这里就以07版为例:
在这里插入图片描述
下面代码可以单独封装出来,根据类型判断。Excel表里的每一行内容实际也就是一个对象,也可以读出来之后封装到对象里。

@Test
public void testCellType() throws Exception{
    // 获取文件流
    InputStream inputStream = new FileInputStream("D:\\test.xlsx");
    // 创建工作簿
    Workbook workbook = new XSSFWorkbook(inputStream);
    // 创建工作表 - 下标为0
    Sheet sheet = workbook.getSheetAt(0);
    // 获取标题内容 - 第一行
    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){
                String cellValue = cell.getStringCellValue();
                System.out.print(cellValue + "|");
            }
        }
        System.out.println();
    }
    // 获取行数
    int rowCount = sheet.getPhysicalNumberOfRows();
    for (int rowNum = 1; rowNum < rowCount; rowNum++){
        // 获取行
        Row row = sheet.getRow(rowNum);
        if (row != null){
            // 获取列数
            int cellCount = row.getPhysicalNumberOfCells();
            for (int cellNum = 0; cellNum < cellCount; cellNum++){ // 遍历列
                Cell cell = row.getCell(cellNum);
                String value = "";
                if (cell != null){
                    int cellType = cell.getCellType(); // 获取单元格数据类型
                    switch (cellType){
                        case HSSFCell.CELL_TYPE_STRING:   // 字符串类型
                            System.out.print("【String类型】");
                            value = cell.getStringCellValue();
                            break;
                        case HSSFCell.CELL_TYPE_BOOLEAN:  // 布尔类型
                            System.out.println("【boolean类型】");
                            value = String.valueOf(cell.getBooleanCellValue());
                            break;
                        case HSSFCell.CELL_TYPE_BLANK:  // 为空
                            break;
                        case HSSFCell.CELL_TYPE_NUMERIC:  // 数字(日期、普通数字)
                            if (HSSFDateUtil.isCellDateFormatted(cell)){  // 日期
                                System.out.println("【Date日期】");
                                value = new DateTime(cell.getDateCellValue()).toString("yyyy-MM-dd");
                            }else{      // 数字
                                System.out.println("【纯数字】");
                                cell.setCellType(HSSFCell.CELL_TYPE_STRING);
                                value = String.valueOf(cell.toString());
                            }
                            break;
                        case HSSFCell.CELL_TYPE_ERROR:
                            System.out.println("【数据类型错误】");
                            break;
                    }
                    System.out.println(value);
                }
            }
        }
    }
}

结果:
Excel - POI与EasyExcel操作Excel表_第3张图片

EsayExcel - 推荐使用

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模式。在上层做了模型转换的封装,让使用者更加简单方便。

EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。

EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)。

写操作

先创建一个实体类

@Data
public class User {
    @ExcelProperty("学生编号")
    private Long id;
    @ExcelProperty("学生姓名")
    private String name;
    @ExcelProperty("入学时间")
    private Date date;
    @ExcelProperty("入学成绩")
    private Double grade;
    @ExcelProperty("是否入学")
    private Boolean isOk;
    @ExcelIgnore  // 忽略
    private String sex;
}

创建写的测试类

@Test
public void testWrite(){
    String fileName = "D:\\write.xlsx";
    EasyExcel.write(fileName, User.class).sheet("学生列表").doWrite(getDate());
}

/**
 * 获取数据
 */
public static List<User> getDate(){
    List<User> list = new ArrayList<User>();
    User user;
    for (int i = 1; i <= 10; i++){
        user = new User();
        user.setId((long)i);
        user.setName("学生" + i + "号");
        user.setDate(new Date());
        user.setGrade(99.99);
        user.setIsOk(i%2==0);
        user.setSex((i%2==0)?"男":"女");
        list.add(user);
        System.out.println(user);
    }
    return list;
}

结果:
Excel - POI与EasyExcel操作Excel表_第4张图片

读操作

先创建一个监听器

public class ExcelListener extends AnalysisEventListener<User> {

    private List<User> users = new ArrayList<User>();
    // 让外界可以调用
    public List<User> getUser() {
        return users;
    }

    // 一行一行读取Excel内容
    @Override
    public void invoke(User user, AnalysisContext analysisContext) {
        System.out.println("读取中...");
        users.add(user);  // 加入到集合
    }

    // 读取表头内容
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        System.out.println("表头:" + headMap);
    }

    // 读取完成之后做什么事
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        System.out.println("读完了");
    }
}

测试代码

@Test
public  void testRead() {
    // 实现Excel读操作
    String fileName = "D:\\write.xlsx";
    ExcelListener excelListener = new ExcelListener();
    EasyExcel.read(fileName,User.class,excelListener).sheet().doRead();
    List<User> users = excelListener.getUser();
    for (User user : users) {
        System.out.println(user);
    }
}

结果(sex已经被忽略,所以为null):
Excel - POI与EasyExcel操作Excel表_第5张图片

实际开发中Excel的的读取

因为在实际开发中,我们通常要调用某个服务模块,把从Excel里读取的数据存入到数据库。因此,修改监听器代码如下:

public class ExcelListener extends AnalysisEventListener<User> {

    // 用于控制存储数据库
    private static int BATCH_COUNT = 100;
    // 用于存储数据
    private List<User> users = new ArrayList<User>();

    // 在这个监听器类没有办法通过注解的方式注入Service,只能通过参数传递
    private UserService userService;
    
    public ExcelListener(){}
    
    public ExcelListener(UserService userService){
        this.userService = userService;
    }
    
    // 一行一行读取Excel内容
    @Override
    public void invoke(User user, AnalysisContext analysisContext) {
        users.add(user);
        // 达到BATCH_COUNT了,需要存入一次数据,防止数据太多在内存,导致OOM
        if(users.size() >= BATCH_COUNT){
            userService.saveUsers(users);  // 保存一次
            users.clear();   // 清空List集合
        }
    }

    // 读取表头内容
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        System.out.println("表头:" + headMap);
    }

    // 读取完成之后做什么事
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        System.out.println("读完了");
    }
}

你可能感兴趣的:(第三方服务及工具,POI,EasyExcel,Excel操作)