poi和easyExcel使用

Apache poi

官网https://poi.apache.org/
Apache poi是Apache软件基金会的开放源码函式库,poi提供API给Java程序对Microsoft Office格式档案读和写的功能。

基本功能:
HSSF - 提供读写Microsoft Excel格式档案的功能。
XSSF - 提供读写Microsoft Excel OOXML格式档案的功能。
HWPF - 提供读写Microsoft Word格式档案的功能。
HSLF - 提供读写Microsoft PowerPoint格式档案的功能。
HDGF - 提供读写Microsoft Visio格式档案的功能。

easyExcel

官网https://github.com/alibaba/easyexcel/

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模式。在上层做了模型转换的封装,让使用者更加简单方便。
64M内存1分钟内读取75M(46W行25列)的Excel
当然还有急速模式能更快,但是内存占用会在100M多一点 。

内存问题:
POI:会将数据先加载到内存(数据量大会导致OOM问题),再将数据写入到文件中去。

在这里插入图片描述
excel是有03版和07版本之分,03版本后缀名是xls,限制65536行,07版本后缀名是xlsx,没有限制行数。

poi测试

pom.xml导入依赖

		<!-- xls03版 -->
		<dependency>
			<groupId>org.apache.poi</groupId>
			<artifactId>poi</artifactId>
			<version>5.0.0</version>
		</dependency>
		<!-- xlsx07版 -->
		<dependency>
			<groupId>org.apache.poi</groupId>
			<artifactId>poi-ooxml</artifactId>
			<version>5.0.0</version>
		</dependency>
		<!-- 日期格式化工具 -->
		<dependency>
			<groupId>joda-time</groupId>
			<artifactId>joda-time</artifactId>
			<version>2.10.4</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter-api</artifactId>
			<version>5.7.1</version>
			<scope>test</scope>
		</dependency>

03版本写入的测试方法,用的类是HSSFWorkbook,文件后缀名是xls。

	@Test
    public void test() throws IOException {
        //03版本excel
        //创建一个工作簿
        Workbook workbook = new HSSFWorkbook();
        //创建工作表
        Sheet sheet = workbook.createSheet("测试工作表");
        //创建行,默认是从0开始
        Row row1 = sheet.createRow(0);
        //创建一个单元格,跟行结合是第一行第一列
        Cell cell1 = row1.createCell(0);
        //插入数据
        cell1.setCellValue("测试数据");

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

        //生成一张表
        FileOutputStream outputStream = new FileOutputStream("C:\\Users\\wdn\\Desktop\\测试.xls");
        workbook.write(outputStream);
        //关闭流
        outputStream.close();
    }

07版本测试写入方法,用的类是XSSFWorkbook,文件后缀名是xlsx。

	@Test
    public void test() throws IOException {
        //07版本excel
        //创建一个工作簿
        Workbook workbook = new XSSFWorkbook();

        //创建工作表
        Sheet sheet = workbook.createSheet("测试工作表");
        //创建行,默认是从0开始
        Row row1 = sheet.createRow(0);
        //创建一个单元格,跟行结合是第一行第一列
        Cell cell1 = row1.createCell(0);
        //插入数据
        cell1.setCellValue("测试数据");

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

        //生成一张表
        FileOutputStream outputStream = new FileOutputStream("C:\\Users\\wdn\\Desktop\\测试.xlsx");
        workbook.write(outputStream);
        //关闭流
        outputStream.close();
    }

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

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

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

03版本读取的测试方法

	 //文件路径
    static String path = "C:\\Users\\wdn\\Desktop\\测试.xls";
    @Test
    public void test() throws IOException {
        FileInputStream inputStream = new FileInputStream(path);
        //创建工作簿
        Workbook workbook = new HSSFWorkbook(inputStream);
        //获取表,每一个工作簿至少有一个工作表
        Sheet sheetAt = workbook.getSheetAt(0);
        //获取行数
        int rowCount = sheetAt.getPhysicalNumberOfRows();
        for (int i = 0; i < rowCount; i++) {
            //获取每一行对象
            Row row = sheetAt.getRow(i);
            if(row != null){
                //获取列数
                int cellCount = row.getPhysicalNumberOfCells();
                for (int j = 0; j < cellCount; j++) {
                    //获取列对象
                    Cell cell = row.getCell(j);
                    if(cell != null){
                        switch (cell.getCellType()){
                            case STRING: //字符串
                                System.out.println(cell.getStringCellValue());
                                break;
                            case NUMERIC: //数字(日期和普通数字)
                                if(DateUtil.isCellDateFormatted(cell)){//判断该列是否是日期
                                    System.out.println(new DateTime(cell.getDateCellValue()).toString("yyy-MM-dd HH:mm:ss"));
                                }else{
                                    System.out.println(cell.getNumericCellValue());
                                }
                                break;
                            case BLANK: //空
                                System.out.println("");
                                break;
                        }
                    }
                }
            }
        }
    }

07版本读取的测试方法跟03版本差不多,这里就不写了,主要是要把文件路径改成.xlsx后缀名的文件,以及类HSSFWorkbook改成XSSFWorkbook

easyExcel

pom.xml依赖导入

		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>easyexcel</artifactId>
			<version>2.2.7</version>
		</dependency>

官网的例子:
先创建个基础数据类

/**
 * 基础数据类
 **/
@Data
public class DemoData {
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ExcelProperty("数字标题")
    private Double doubleData;
    /**
     * 忽略这个字段
     */
    @ExcelIgnore
    private String ignore;
}

EasyExcel写入测试方法

	//路径
	static String path = "C:\\Users\\wdn\\Desktop\\";
    @Test
    public void simpleWrite() {
        // 写法1
        String fileName = path + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        // 如果这里想使用03 则 传入excelType参数即可
        EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());

        // 写法2
        fileName = path + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去写
        ExcelWriter excelWriter = null;
        try {
            excelWriter = EasyExcel.write(fileName, DemoData.class).build();
            WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
            excelWriter.write(data(), writeSheet);
        } finally {
            // 千万别忘记finish 会帮忙关闭流
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }
    }

    public List<DemoData> data(){
        List<DemoData> list = new ArrayList<>();
        DemoData demoData = new DemoData();
        demoData.setString("你好,小young");
        demoData.setDate(new Date());
        demoData.setDoubleData(18.18);
        demoData.setIgnore("忽略");
        list.add(demoData);
        return list;
    }

EasyExcel读取测试方法
首先创建监听器类,里面的监听方法可以根据自己需求,具体参考官网模板

/**
 * 模板的读取类
 */
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
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>();

    /**
     * 这个每一条数据解析都会来调用
     *
     * @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) {
            list.stream().forEach(item->{
                System.out.println(item.toString());
            });
            // 存储完成清理 list
            list.clear();
        }
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        LOGGER.info("所有数据解析完成!");
    }
}

具体测试方法

 	static String path = "C:\\Users\\wdn\\Desktop\\";
 	@Test
    public void simpleRead() {
        // 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
        // 写法1:
        String fileName = path + "demo.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();

        // 写法2:
        //fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        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();
            }
        }
    }

总结

此篇有参考b站狂神说的poi和easyExcel视频,大家也可以去了解了解,具体easyExcel的用法大家还是要参考官网的源码,在此祝愿大家编程路上bug越写越少,只要你够强,bug就追不上你(开玩笑)。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值