POI和easyExcel的讲解和使用

目录

应用场景:

[Apache POI:](#Apache POI:)

EasyExcel:

POI具体实现:

[POI-Excel 写:](#POI-Excel 写:)

[POI-Excel 读:](#POI-Excel 读:)

大文件写HSSF:

大文件写XSSF:

大文件写SXSSF:

EasyExcel:

导入相关依赖:

Write:

Read:


应用场景:

1、将用户信息导出为excel表格

2、将Excel表中的信息录入到网站数据库

开发中经常会涉及到excel的处理,如导出Excel,导入Excel到数据库中

操作Ecxel目前比较流行的iu是Apache POI 和阿里巴巴的 easyExcel

Apache POI:

EasyExcel:

他是阿里巴巴开源的一个框架,使用简单,节省内存

EasyExcel能大大减少内存占用的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行一行读取数据,逐个解析。

easyExcel中基本不会出现内存溢出

POI具体实现:

POI-Excel 写:

excel中分为两个版本 03 和 07,03版只能存储65536条数据,07版理论上可以存储无数条数据

代码在idea中 区分03 07 版本,03版本和07版本使用的区别只在 1、创建使用的关键字不一样【可以参上我上面发的结构图】 2、文件的后缀不一样

java 复制代码
@Test
void testWrite03() throws IOException {

    String PATH = ("E:\\Javastudy\\teshuchengzhang\\Code\\Spring\\PoiandEasyExceldemo\\src\\main\\java\\com\\example\\poiandeasyexceldemo\\controller");

    //工作簿 03版        这里用的是HSSF    如果是07的话就用XSSF
    Workbook workbook = new HSSFWorkbook();
    //工作表
    Sheet sheet = workbook.createSheet("统计表");
    //创建一个行 0 就是第一行
    Row row1 = sheet.createRow(0);
    //单元格(1,1)
    Cell cell = row1.createCell(0);
    cell.setCellValue("今日新增观众");
    //单元格(1,2)
    Cell cell2 = row1.createCell(1);
    cell2.setCellValue("今日收入");
    //创建第二行
    Row row2 = sheet.createRow(1);
    Cell cell21 = row2.createCell(0);
    cell21.setCellValue("统计时间");
    //(2.2)
    Cell cell22  =row2.createCell(1);
    String time = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
    cell22.setCellValue(time);

    //生成一张表(IO)流  03版本使用的是xls结尾
   FileOutputStream fileOutputStream= new FileOutputStream(PATH+"案例03.xls");
    workbook.write(fileOutputStream);
    //关闭流
    System.out.println("生成完毕");
}

POI-Excel 读:

读取不同数据类型的数据:

java 复制代码
@Test
public void GetDiffTypeTest() throws IOException {
    //获取文件流
    FileInputStream inputStream = new FileInputStream("E:\\Javastudy\\teshuchengzhang\\Code\\Spring\\PoiandEasyExceldemo\\src\\main\\java\\com\\example\\poiandeasyexceldemo\\biaoge测试表格.xlsx");
    //创建一个工作簿
    Workbook workbook = new XSSFWorkbook(inputStream);
    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){
                int cellType = cell.getCellType();
                String cellValue = cell.getStringCellValue();
                System.out.print(cellValue+"|");

            }
        }
        System.out.println();
    }
    //获取表中的内容
    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.println("["+(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);
                }
            }
        }
    }
    inputStream.close();
}

大文件写HSSF:

缺点:最多只能处理65536行,否则会抛出异常

优点:过程中写入缓存,不操作磁盘,最后一次性写入磁盘速度快

大文件写XSSF:

缺点:写数据时速度非常慢,非常耗内存,也会发生内存泄露,如100万条

优点:可以写较大数据量,如20万条

大文件写SXSSF:

优点:可以写非常大的数据量,如100万条甚至更多条,写数据速度快,占用更少的内存

代码:

java 复制代码
@Test
public void testWrite07BigDatas() throws IOException {
    //07 使用SXSSFWorkbook来实现。
    String PATH = "E:\\Javastudy\\teshuchengzhang\\Code\\Spring\\PoiandEasyExceldemo\\src\\main\\java\\com\\example\\poiandeasyexceldemo\\controller";
    //时间
    System.out.println(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<10;cellNum++){
            Cell cell  =row.createCell(cellNum);
            cell.setCellValue(cellNum);
        }
    }
    System.out.println("over");
    System.out.println(System.currentTimeMillis());

    FileOutputStream outputStream =new FileOutputStream(PATH+"testWrite07BIgData.xlsx");
    workbook.write(outputStream);
    outputStream.close();
    //还需要添加一步:清除临时文件
    ((SXSSFWorkbook)workbook).dispose();
    System.out.println("over");
    System.out.println(System.currentTimeMillis());

}

注意:

  • 过程中会产生临时文件,需要清理临时文件
  • 默认由100条记录被保存在内存中,如果超过这数量,则最前面的数据被写入临时文件
  • 如果想自定义内存中数据的数量,可以使用new SXSSFWorkbook(数量)

EasyExcel:

我们如果使用easyExcel的话,就不再需要去单独导入POI的依赖了,点进easyExcel的依赖 那里就包含了POI的依赖了。

导入相关依赖:

java 复制代码
<dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
    <version>2.10.1</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.2.0-beta2</version>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.34</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.83</version>
</dependency>

Write:

首先创建实体类:

java 复制代码
@Getter
@Setter
@EqualsAndHashCode
public class DemoData {
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ExcelProperty("数字标题")
    private Double doubleData;
    /**
     * 忽略这个字段
     */
    @ExcelIgnore
    private String ignore;
}

测试类中的代码:

java 复制代码
@SpringBootTest
public class EasyExcelTest {
    String PATH = "E:\\Javastudy\\teshuchengzhang\\Code\\Spring\\PoiandEasyExceldemo\\src\\main\\java\\com\\example\\poiandeasyexceldemo\\biaoge";

    private List<DemoData> data() {
        List<DemoData> list = new ArrayList<DemoData>();
        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;
    }
    @Test
    public void easyExcel() {

        //测试成功--------------------------------------------------------------------

        // 注意 simpleWrite在数据量不大的情况下可以使用(5000以内,具体也要看实际情况),数据量大参照 重复多次写入

        // 写法1 JDK8+
        // since: 3.0.0-beta1
        String fileName = PATH+"测试表格.xlsx";
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        // 如果这里想使用03 则 传入excelType参数即可
        EasyExcel.write(fileName, DemoData.class)
                .sheet("模板")
                .doWrite(data());

    }
}

Read:

实体类代码:

java 复制代码
@Data
public class DemoData {
    private String string;
    private Date date;
    private Double doubleData;
}

持久层代码:

java 复制代码
public class DemoDAO {
    public void save(List<DemoData> list) {
        // 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入
    }
}

监听器代码:

java 复制代码
public 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) {
        System.out.print(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("存储数据库成功!");
    }
}

测试类代码:

java 复制代码
@SpringBootTest
public class EasyExcelRead {
    String PATH = "E:\\Javastudy\\teshuchengzhang\\Code\\Spring\\PoiandEasyExceldemo\\src\\main\\java\\com\\example\\poiandeasyexceldemo\\biaoge";
    @Test
    public void simpleRead() {
        //完成----------------------
        String fileName = PATH+"测试表格.xlsx";
        // 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
        // 写法1:
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
    }
}
相关推荐
蓝澈112119 分钟前
迪杰斯特拉算法之解决单源最短路径问题
java·数据结构
Kali_0726 分钟前
使用 Mathematical_Expression 从零开始实现数学题目的作答小游戏【可复制代码】
java·人工智能·免费
rzl0238 分钟前
java web5(黑马)
java·开发语言·前端
君爱学习43 分钟前
RocketMQ延迟消息是如何实现的?
后端
guojl1 小时前
深度解读jdk8 HashMap设计与源码
java
Falling421 小时前
使用 CNB 构建并部署maven项目
后端
guojl1 小时前
深度解读jdk8 ConcurrentHashMap设计与源码
java
程序员小假1 小时前
我们来讲一讲 ConcurrentHashMap
后端
爱上语文1 小时前
Redis基础(5):Redis的Java客户端
java·开发语言·数据库·redis·后端