EasyExcel的使用学习

EasyExcel简介:

EasyExcel是阿里巴巴开源的一个excel处理框架,是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。
github地址:https://github.com/alibaba/easyexcel

Java领域解析、生成Excel比较有名的框架有Apache POI、jxl等。但它们都存在一个严重的问题就是非常的耗内存。如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc。

首先介绍一下什么是OOM?

oom就是我们常说的内存溢出,它是指需要的内存空间大于系统分配的内存空间,oom后果就是项目程序crash;

常见造成oom的原因?

内存泄漏造成;加载的文件或者图片过大造成;

解决方案:

内存泄漏是造成内存溢出的一个原因,所以避免内存泄漏的那些方法都适用与内存溢出,比如:及时回收无用的引用对象,资源回收等......

对于图片方面,如果加载过大的图片要将图片转出bitmap要压缩,异步加载图片(需要的图片或者说页面要显示的图片先加载出来),在listview中consView和Viewholder 一起使用

想具体了解OOM,可以具体了解:常见9种 OOM 原因及解决方案

EasyExcel的特点和原理:

64M内存1分钟内读取75M(46W行25列)的Excel

当然还有急速模式能更快,但是内存占用会在100M多一点

EasyExcel的使用学习_第1张图片

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

下图是easyExcel和POI在解析Excel时的对比图。

EasyExcel的使用学习_第2张图片

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

EasyExcel的使用学习_第3张图片

快速使用指南:

1.maven:


        
        
            com.alibaba
            easyexcel
            2.2.2
        
        
        
            commons-fileupload
            commons-fileupload
            1.4
        

2. 注解

ExcelProperty    指定当前字段对应excel中的那一列。可以根据名字或者Index去匹配。不写,默认按顺序匹配
        value     名头名称
        index    字段与列数对应索引
        converter    字段转化器
ExcelIgnore    默认所有字段都会和excel去匹配,加了这个注解会忽略该字段
DateTimeFormat    日期转换,用String去接收excel日期格式的数据会调用这个注解。
        value  SimpleDateFormat格式方式 y(年) M(月 )d(日) h(时) m(分) s(秒)
        use1904windowing 是否使用1904windowing
NumberFormat    数字转换,用String去接收excel数字格式的数据会调用这个注解。
       value    DecimalFormat格式方式 ##.00
       roundingMode    取舍方式
ExcelIgnoreUnannotated    默认不加ExcelProperty 的注解的都会参与读写,加了不会参与
ColumnWidth    设置宽
        value    数值
ContentRowHeight    设置高
        value    数值
HeadRowHeight    设置头高
        value    数值

 

3.文件上传读取Excel

示例代码:

DEMO代码地址:https://github.com/alibaba/easyexcel/blob/master/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java

最简单的web中的读:

excel示例:

EasyExcel的使用学习_第4张图片

step1:建立读取excel存取数据的对象


@Data
public class DemoData {

    @ExcelProperty("测评人身份证号")
    private String assessorId;

    @ExcelProperty("被测评人身份证号")
    private String assesseeId;

    @ExcelProperty("测评评价")
    private String chkResult;

    @ExcelProperty("测评成绩")
    private String chkGrade;

    @ExcelIgnore
    private String ignore;


}

step2:监听器,用于监听excel中的每一行数据

 


@Component
@Scope("prototype")
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
public class DemoDataListener extends AnalysisEventListener {
    private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class);
    /**
     * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;
    List list = new ArrayList();
    /**
     * 这个是一个service,也可以是一个dao。当然如果不用存储这个对象没用。
     */
    @Autowired
    private CadreAssessmentService cadreAssessmentService;

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

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


}

step3:service层,用于处理业务逻辑

public interface CadreAssessmentService {
    void saveData(List list);
}

@Service
public class CadreAssessmentServiceImpl implements CadreAssessmentService {
 
    /**
     *这是dao,用于存储数据到数据库
     */
    @Autowired
    private PerEvaRepository perEvaRepository;
   

    @Override
    public void saveData(List list) {
       
        for (DemoData demoData : list) {
            //数据库对应的表domain实体类
            PersonEvaluateDtl personEvaluateDtl = new PersonEvaluateDtl();
            personEvaluateDtl.setAssessorId(demoData.getAssessorId());
            personEvaluateDtl.setAssesseeId(demoData.getAssesseeId());
            personEvaluateDtl.setChkResult(demoData.getChkResult());
            personEvaluateDtl.setChkGrade(demoData.getChkGrade());
            personEvaluateDtl.setYear(new Date());
            perEvaRepository.saveAll(personEvaluateDtl);
        }
    }

step4:持久层,用于保存数据到数据库

@Repository
public interface PerEvaRepository  extends JpaRepository, JpaSpecificationExecutor {

}

controller层代码示例:

    @Autowired
    private DemoDataListener demoDataListener;

    
    /**
     * 最简单的读
     * 

1. 创建excel对应的实体对象 参照{@link DemoData} *

2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} *

3. 直接读即可 */ @PostMapping(value = "/import") public ResponseEntity simpleRead(MultipartFile uploadFile){ try { EasyExcel.read(uploadFile.getInputStream(), DemoData.class, demoDataListener).sheet().doRead(); } catch (IOException e) { e.printStackTrace(); } return ResponseEntity.status(HttpStatus.CREATED).build(); }

4.文件下载最简单的写:

示例代码

DEMO代码地址:https://github.com/alibaba/easyexcel/blob/master/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java

excel示例

img

step1:对象

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

step2:TestDemo

 /**
     * 最简单的写
     * 

1. 创建excel对应的实体对象 参照{@link DemoData} *

2. 直接写即可 */ @GetMapping(value = "/simpleWrite") public void simpleWrite(HttpServletResponse response) throws IOException { //可直接用浏览器或者用postman response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("utf-8"); // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系 String fileName = URLEncoder.encode("测试", "UTF-8"); response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx"); ServletOutputStream outputStream = response.getOutputStream(); EasyExcel.write(outputStream, DemoData.class).sheet("模板").doWrite(data()); } //通用数据生成后面不会重复写 private List data() { List list = new ArrayList(); 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; }

在导出Excel的部分,easyExcel还提供了自定义样式,插入表格,插入图片等其他功能,还有一个比较有意思的功能就是Excel模板填充的功能。详细的功能信息参考官方文档:https://www.yuque.com/easyexcel/doc/easyexcel

你可能感兴趣的:(Java)