zoukankan      html  css  js  c++  java
  • 如何让excel文件读取变得更简单

    今天给大家安利一款excel文件导入神器,easyexcel,官方地址:(https://github.com/alibaba/easyexcel)。
    在官网文档中有介绍了其性能。
    poi vs easyexcel 内存消耗对比2003
    poi vs easyexcel 内存消耗对比2007
    poi vs easyexcel 用时 2003
    poi vs easyexcel 用时 2007
    从上面的性能测试可以看出easyexcel在解析耗时上比poiuserModel模式弱了一些。主要原因是我内部采用了反射做模型字段映射,中间我也加了cache,但感觉这点差距可以接受的。但在内存消耗上差别就比较明显了,easyexcel在后面文件再增大,内存消耗几乎不会增加了。但poi userModel就不一样了,简直就要爆掉了。想想一个excel解析200M,同时有20个人再用估计一台机器就挂了。

    如何使用呢

    1、引入maven依赖

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

    由于改jar包对poi进行了一些封装,因此需要注释掉项目引用的poi依赖,不然会有版本冲突。
    2、创建接收excel数据的实体

    @Data
    public class Person {
    
        @ExcelProperty(value = "姓名",index = 1)
        private String name;
    
        @ExcelProperty("性别")
        private String sex;
    
        @ExcelProperty("年龄")
        private int age;
    }
    

    @ExcelProperty 这个注解用于指定该属性对应excel文件中的哪一列数据。里面有两个属性,一个是value,另一个是index(从0开始),这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配。
    3、增加person监听器

    @Slf4j
    public class PersonListener extends AnalysisEventListener<Person> {
    
        /**
         * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
         */
        private static final int BATCH_COUNT = 5;
        List<Person> list = new ArrayList();
    
        @Override
        public void invoke(Person person, AnalysisContext analysisContext) {
            log.info("解析到一条数据:{}",person);
            if (list.size() >= BATCH_COUNT) {
                saveData();
                list.clear();
            }
        }
    
        @Override
        public void doAfterAllAnalysed(AnalysisContext analysisContext) {
            saveData();
            log.info("所有数据解析完成!");
        }
        @Override
        public void onException(Exception exception, AnalysisContext context) {
            log.error("解析失败,但是继续解析下一行", exception);
    
        }
        /**
         * 加上存储数据库
         */
        private void saveData(){
            log.info("{}条数据,开始存储数据库!", list.size());
            log.info("存储数据库成功!");
        }
    }
    

    4、文件上传方法

    @RestController
    @Slf4j
    public class PersonController {
        
        @PostMapping("importFile")
        public String readPerson(MultipartFile file){
            
            PersonListener personListener = new PersonListener();
            try {
    
                // headRowNumber(2) 这里可以设置1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,他没有指定头,也就是默认1行
                EasyExcel.read(file.getInputStream(), Person.class,personListener).sheet().headRowNumber(1).doRead();
    
            }catch (IOException ioe){
    
                log.info("读取excel异常={}",ioe);
            }
            return "";
        }
    }
    

    这样代码基本就算完成了。接下来我们看看如何在

    spring框架中使用

    我们知道,在spring中文件入库的时候可能需要调用service,service调用dao入库。那我们如何在这个PersonListener中调用service呢。
    首先,在PersonController中注入service.

        @Autowired
        private PersonService personService;
    

    然后,在PersonListener中增加一个有参构造器。

       private PersonService personService;
        
        public PersonListener(PersonService personService){
    
            this.personService = personService;
        }
    

    最后在PersonController中new PersonListener的时候将service传进来即可。

    	@Autowired
        private PersonService personService;
        @PostMapping("importFile")
        public String readPerson(MultipartFile file){
    
            PersonListener personListener = new PersonListener(personService);
            try {
    
                // headRowNumber(2) 这里可以设置1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,他没有指定头,也就是默认1行
                EasyExcel.read(file.getInputStream(), Person.class,personListener).sheet().headRowNumber(2).doRead();
    
            }catch (IOException ioe){
    
                log.info("读取excel异常={}",ioe);
            }
            return "";
        }
    

    接下来我们在看一下,有一个需求是这样的,我们需要统计一下导入成功数和失败数并且要不失败数写入到一个excel中返回给页面,如何实现?
    我们调整一下PersonListener

    @Slf4j
    public class PersonListener extends AnalysisEventListener<Person> {
    
        private int successCount = 0; // 成功量
        
        private int exceptionCount = 0; // 异常量
    
        private List<Person> exceptionList = new ArrayList<>(); // 异常数据
    
        /**
         * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
         */
        private static final int BATCH_COUNT = 5;
        List<Person> list = new ArrayList();
    
        private PersonService personService;
    
        public PersonListener(PersonService personService){
    
            this.personService = personService;
        }
    
        @Override
        public void invoke(Person person, AnalysisContext analysisContext) {
            log.info("解析到一条数据:{}",person);
            successCount++;
    				list.add(person);
            if (list.size() >= BATCH_COUNT) {
                saveData();
                list.clear();
            }
        }
    
        @Override
        public void doAfterAllAnalysed(AnalysisContext analysisContext) {
            saveData();
            log.info("所有数据解析完成!");
        }
        @Override
        public void onException(Exception exception, AnalysisContext context) {
            log.error("解析失败,但是继续解析下一行", exception);
    
            Person person  = (Person)context.readRowHolder().getCurrentRowAnalysisResult();
            exceptionList.add(person);
            exceptionCount++;
    
        }
        /**
         * 加上存储数据库
         */
        private void saveData(){
            log.info("{}条数据,开始存储数据库!", list.size());
            log.info("存储数据库成功!");
        }
        /**
         * 插入结果返回
         * @return
         */
        public Map<String,Object> getData(){
    
            Map<String,Object> map = new HashMap<>();
            map.put("success",successCount);
            map.put("exception",exceptionCount);
            return map;
        }
    
        /**
         * 失败数据返回
         * @return
         */
        public List<Person> getExceptionList(){
    
            return exceptionList;
        }
    }
    

    在PersonController中将异常数据写入文件。

    @RestController
    @Slf4j
    public class PersonController {
    
        @Autowired
        private PersonService personService;
    
        @PostMapping("importFile")
        public String readPerson(MultipartFile file){
    
            PersonListener personListener = new PersonListener(personService);
            try {
    
                // headRowNumber(1) 这里可以设置1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,他没有指定头,也就是默认1行
                EasyExcel.read(file.getInputStream(), Person.class,personListener).sheet().headRowNumber(1).doRead();
    
            }catch (IOException ioe){
    
                log.info("读取excel异常={}",ioe);
            }
            Map<String, Object> data = personListener.getData();
            String exception = data.get("exception") + "";
            int exceptionCount = Integer.parseInt(exception);
            if(exceptionCount>0){
                String fileName = System.currentTimeMillis() + ".xlsx";
                // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
                EasyExcel.write("E://upload/file/"+fileName, Person.class).sheet("模板").doWrite(personListener.getExceptionList());
                data.put("fileName",fileName);
            }
            return data.toString();
        }
    }
    

    我们写个页面测试一下

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>文件上传</title>
    </head>
    <body>
    <form action="/importFile" method="post" enctype="multipart/form-data">
        <input type="file"  name="file"/>
        <button type="submit">提交</button>
    </form>
    </body>
    </html>
    

    下面是测试的日志文件

    2019-10-20 11:21:59.809  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 解析到一条数据:Person(name=执偕, sex=男, age=18)
    2019-10-20 11:21:59.811  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 解析到一条数据:Person(name=执偕2, sex=男, age=19)
    2019-10-20 11:21:59.811  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 解析到一条数据:Person(name=执偕3, sex=男, age=20)
    2019-10-20 11:21:59.811  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 解析到一条数据:Person(name=执偕4, sex=男, age=21)
    2019-10-20 11:21:59.812  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 解析到一条数据:Person(name=执偕5, sex=男, age=22)
    2019-10-20 11:21:59.812  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 5条数据,开始存储数据库!
    2019-10-20 11:21:59.812  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 存储数据库成功!
    2019-10-20 11:21:59.812  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 解析到一条数据:Person(name=执偕6, sex=男, age=23)
    2019-10-20 11:21:59.812  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 解析到一条数据:Person(name=执偕7, sex=男, age=24)
    2019-10-20 11:21:59.813  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 解析到一条数据:Person(name=执偕8, sex=男, age=25)
    2019-10-20 11:21:59.813  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 解析到一条数据:Person(name=执偕9, sex=男, age=26)
    2019-10-20 11:21:59.813  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 4条数据,开始存储数据库!
    2019-10-20 11:21:59.813  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 存储数据库成功!
    2019-10-20 11:21:59.813  INFO 17748 --- [nio-8080-exec-1] c.zhixie.easyexcel.demo.PersonListener   : 所有数据解析完成!
    

    今天就介绍到这了,文中的代码我已上传到码云上,
    地址:https://gitee.com/javaXiaoCaiJi/zhixie-code-example/tree/master/easyexcel

    如果文章对您有帮助,请记得点赞关注哟~
    欢迎大家关注我的公众号<情系IT>,每日技术推送文章供大家学习参考。

  • 相关阅读:
    CSS 动画示例
    CSS3 学习过程中还需要再次重点的
    CSS3 transform
    CSS 用before和after伪类选择器制作阴影
    CSS 制作的导航菜单
    Iconfont-阿里巴巴矢量库下载的字体
    HTML 5 <meta> 标签
    采购订单暂存和持有相关的问题?
    JS 基础
    LaTeX+TexStudio安装与使用
  • 原文地址:https://www.cnblogs.com/zhixie/p/11717739.html
Copyright © 2011-2022 走看看