[JAVA工具类]-大数据Excel生成导出

前言

在实际工作中,经常会需要进行Excel文件的下载导出,并且有时希望通过异步下载来进行实现或者需要下载数据量很大。为防止各个系统重复造轮子,本文通过注解方式来实现Excel的普通、分片生成。

依赖Jar包

            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi-ooxml</artifactId>
                <version>3.17</version>
            </dependency>
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi</artifactId>
                <version>3.17</version>
            </dependency>

实现方案

1、通过自定义注解来定义导出文件的Bean对象。
2、自定义注解可以设定导出字段的标题名称。
3、创建一个通用的Excel导出工具类,采用SXSSFWorkbook实现大数据量的Excel生成
4、满足普通生成和分片生成功能,分片上传通过内存缓存已经生成的ExcelWorkBook,然后后续分片追加,最后完成时删除缓存的文件。

直接上代码

1、自定义注解
/**
 * Excel导出字段 
 * @author yukaiji
 * @date 2020-05-20
 */
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface XlsField {

    String xlsHeaderName() default "";

}
2、自定义注解使用方式
    public class Test {
        @XlsField(xlsHeaderName = "姓名")
        String name;
        @XlsField(xlsHeaderName = "年龄")
        String age;
        @XlsField(xlsHeaderName = "性别")
        String sex;
    }
3、Excel生成工具类
import org.apache.commons.lang3.ArrayUtils;
import org.apache.poi.util.IOUtils;
import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;

import java.io.*;
import java.lang.reflect.Field;
import java.util.*;

/**
 * 通过SXSSFWorkbook实现一个大数据excel生成工具类
 * 版本要求excel2007之后版本
 * 扩展名为.xlsx
 *
 * @author yukaiji
 * @date 2020-05-20
 */
public class ExcelUtil {


    /**
     * 用来做分片上传,以文件名称为key,已经生成过的workBook为value
     **/
    private static Map<String, LocalWorkbook> FILE_BOOK_MAP = new HashMap<>(64);

    /**
     * 单个Sheet页最大行数
     **/
    private static final int MAX_ROW_NUM = 1048574;

    /**
     * 根据自定义注解获取excel表头
     **/
    private static <T> List<String> genHeader(Class<T> modelClazz) {
        Field[] fields = modelClazz.getDeclaredFields();
        if (ArrayUtils.isEmpty(fields)) {
            return new ArrayList(0);
        } else {
            List<String> headers = new ArrayList(fields.length);
            Field[] arr$ = fields;
            int len$ = fields.length;
            for (int i$ = 0; i$ < len$; ++i$) {
                Field field = arr$[i$];
                boolean isPresent = field.isAnnotationPresent(XlsField.class);
                if (isPresent) {
                    String headerInfo = field.getAnnotation(XlsField.class).xlsHeaderName();
                    headers.add(headerInfo);
                }
            }
            return headers;
        }
    }

    /**
     * 创建一个excel文件(非分片)
     *
     * @param models   数据
     * @param fileName 文件名称
     * @return 文件
     */
    public static <T> File createExcel(List<T> models, String fileName) throws IllegalAccessException {
        SXSSFWorkbook workbook = createWorkBook(models, fileName);
        File file = new File(fileName);
        OutputStream out = null;
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
            out = new FileOutputStream(file);
            workbook.write(out);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        } finally {
            IOUtils.closeQuietly(out);
        }
        return file;
    }

    public static <T> File multipartCreateExcel(List<T> models, String fileName, boolean isFinish) throws IllegalAccessException {
        return multipartCreateExcel(models, fileName, MAX_ROW_NUM, isFinish);
    }

    /**
     * 分片生成excel
     *
     * @param models   数据
     * @param fileName 文件名称
     * @param sheetNum 每个Sheet页最大行数
     * @param isFinish 是否生成完成(最后一片)
     * @return 流,可以直接上传S3
     */
    public static <T> File multipartCreateExcel(List<T> models, String fileName, int sheetNum, boolean isFinish) throws IllegalAccessException {
        if (sheetNum > MAX_ROW_NUM) {
            throw new IllegalAccessException("sheet rows num More than " + MAX_ROW_NUM + " rows ");
        }
        SXSSFWorkbook workbook = null;
        try {
            workbook = multipartCreateWorkBook(models, fileName, sheetNum);
        } catch (IllegalAccessException e) {
            FILE_BOOK_MAP.remove(fileName);
            throw e;
        }
        OutputStream out = null;
        File file = new File(fileName);
        if (isFinish) {
            try {
                out = new FileOutputStream(file);
                //临时缓冲区
                //创建临时文件
                workbook.write(out);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                FILE_BOOK_MAP.remove(fileName);
            }
        }
        return file;
    }

    /**
     * 分片写入SXSSFWorkbook
     *
     * @param models      数据
     * @param fileName    文件名称
     * @param sheetRowNum 一个sheet页多少行
     * @return SXSSFWorkbook excel文件
     */
    private static <T> SXSSFWorkbook multipartCreateWorkBook(List<T> models, String fileName, int sheetRowNum) throws IllegalAccessException {
        List<String> header = genHeader(models.get(0).getClass());
        Field[] fields = models.get(0).getClass().getDeclaredFields();
        SXSSFWorkbook workbook;
        SXSSFSheet sheet;
        SXSSFRow row;
        int rowIndex = 0;
        if (!FILE_BOOK_MAP.containsKey(fileName)) {
            workbook = new SXSSFWorkbook(1000);
            sheet = workbook.createSheet();
            row = sheet.createRow(0);
            for (int i = 0; i < header.size(); i++) {
                SXSSFCell cell = row.createCell(i);
                cell.setCellValue(header.get(i));
            }
            FILE_BOOK_MAP.put(fileName, new LocalWorkbook(workbook, rowIndex));
        } else {
            workbook = FILE_BOOK_MAP.get(fileName).getSxssfWorkbook();
            sheet = workbook.getSheetAt(0);
            rowIndex = FILE_BOOK_MAP.get(fileName).getRowIndex();
        }

        Iterator<T> it = models.iterator();
        while (it.hasNext()) {
            if (rowIndex == sheetRowNum) {
                rowIndex = 0;
                sheet = workbook.createSheet();
                row = sheet.createRow(0);
                for (int i = 0; i < header.size(); i++) {
                    SXSSFCell cell = row.createCell(i);
                    cell.setCellValue(header.get(i));
                }
                FILE_BOOK_MAP.get(fileName).setRowIndex(rowIndex);
            }

            rowIndex++;
            row = sheet.createRow(rowIndex);
            T t = (T) it.next();
            int cellIndex = 0;
            for (Field f : fields) {
                SXSSFCell cell = row.createCell(cellIndex);
                f.setAccessible(true);
                boolean isPresent = f.isAnnotationPresent(XlsField.class);
                if (!isPresent) {
                    continue;
                }
                String value = Objects.toString(f.get(t));
                cell.setCellValue(value);
                cellIndex++;
            }
        }
        FILE_BOOK_MAP.get(fileName).setRowIndex(rowIndex);
        return workbook;
    }

    private static <T> SXSSFWorkbook createWorkBook(List<T> models, String fileName) throws IllegalAccessException {
        List<String> header = genHeader(models.get(0).getClass());
        Field[] fields = models.get(0).getClass().getDeclaredFields();
        SXSSFWorkbook workbook = new SXSSFWorkbook(1000);
        SXSSFSheet sheet = workbook.createSheet();
        SXSSFRow row = sheet.createRow(0);
        for (int i = 0; i < header.size(); i++) {
            SXSSFCell cell = row.createCell(i);
            cell.setCellValue(header.get(i));
        }
        Iterator<T> it = models.iterator();
        int index = 0;
        while (it.hasNext()) {
            index++;
            row = sheet.createRow(index);
            T t = (T) it.next();
            int cellIndex = 0;
            for (Field f : fields) {
                SXSSFCell cell = row.createCell(cellIndex);
                f.setAccessible(true);
                boolean isPresent = f.isAnnotationPresent(XlsField.class);
                if (!isPresent) {
                    continue;
                }
                String value = Objects.toString(f.get(t));
                cell.setCellValue(value);
                cellIndex++;
            }
        }
        return workbook;
    }

    /**
     * 分片文件上传文件类
     */
    static class LocalWorkbook {

        private LocalWorkbook(SXSSFWorkbook sxssfWorkbook, int rowIndex) {
            this.sxssfWorkbook = sxssfWorkbook;
            this.rowIndex = rowIndex;
            this.totalRowNum = 0;
        }

        /**
         * 未完成的workBook
         **/
        private SXSSFWorkbook sxssfWorkbook;
        /**
         * 当前sheet页row指针
         **/
        private int rowIndex;
        /**
         * 文件整体的行数
         **/
        private int totalRowNum;

        public SXSSFWorkbook getSxssfWorkbook() {
            return sxssfWorkbook;
        }

        public void setSxssfWorkbook(SXSSFWorkbook sxssfWorkbook) {
            this.sxssfWorkbook = sxssfWorkbook;
        }

        public int getRowIndex() {
            return rowIndex;
        }

        public void setRowIndex(int rowIndex) {
            this.rowIndex = rowIndex;
        }

        public int getTotalRowNum() {
            return totalRowNum;
        }

        public void setTotalRowNum(int totalRowNum) {
            this.totalRowNum = totalRowNum;
        }
    }
}

4、百万数据量测试
public static void main(String[] args) throws IllegalAccessException {
        String fileName = "testexcel.xlsx";
        List<Test> list = new ArrayList<>(1234345);
        for (int i = 0; i < 1234345; i++) {
            list.add(new Test(String.valueOf(i), String.valueOf(i), String.valueOf(i)));
        }
        // 按照200000分片
        List<List<Test>> ss = Lists.partition(list, 200000);
        File file = null;
        for (int i = 0; i < ss.size(); i++) {
            file =  ExcelUtil.multipartCreateExcel(ss.get(i), fileName, 100000, i == ss.size() - 1);
        }
    }

百万数据量的Excel生成大概几秒钟的时间。

总结

就简简单单通过poi包来实现文件的生成,但是只支持excel2007之后的版本,其中通过自定义注解获取要生成的列和表头,生成方式通过传入一个List列表方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值