生成报表和报表导出

背景:
前段时间,项目中需要导出报表,由于报表处理经验不是很丰富,加上需求对接阶段,没有考虑到报表导出的性能问题,当需求做完时,将相关功能打包到服务器上,当下载7000+数据时,导致服务器内存溢出,直接卡死。现网测试阶段直接打回,紧急修改问题,经过简单的度娘查询后,发现报表处理的时候,使用HSSFWorkbook和SXSSFWorkbook效率上差别很大,SXSSFWorkbook是用于处理海量数据时使用,经过本人的测试和比对后,10000条数据的效率<2s,而HSSFWorkbook效率很差劲。数据基数较小时,二者没有任何区别,当数据量大时,请老实选择SXSSFWorkbook,减少被被人喷,哈哈哈,话不多说,先上测试数据效果图。

测试结果GIF图(压缩处理过的)

从上面的GIF图片中可以明显看出,二者的效率,上面测试数据仅为500条。
Maven 依赖包如下(注意三个jar包需要同一版本,且最好是3.5以上的版本):


  org.apache.poi
  poi
  4.0.1


  org.apache.poi
  poi-ooxml
  4.0.1


  org.apache.poi
  

下面给出可直接使用的工具类,
1. HSSFWorkbook的使用

此工具类未做通用处理
代码如下:

package com.lau.module.Utils;

import com.alibaba.druid.util.StringUtils;
import com.lau.module.common.domain.ExcelDomain;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.HorizontalAlignment;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
 * @ClassName HssfExcelUtils
 * @Description HSSFWorkbook处理表格工具类
 * @Author Mr.Lau
 * @Date 2020/05/06 19:49
 * @Version 1.0
 **/
public class HssfExcelUtils {


    /**
     * 创建表头
     * @param tableNames
     * @param sheet
     */
    private static void createTableHeader(String[] tableNames, HSSFSheet sheet)
    {
        HSSFHeader header = sheet.getHeader();
        HSSFRow sheetRow = sheet.createRow(0);
        for(int i=0;i<tableNames.length;i++)
        {
            HSSFCell cell = sheetRow.createCell(i);
            cell.setCellType(CellType.STRING);
            cell.setCellValue(tableNames[i]);
        }
    }

    private static void cereteTableRow(List<String> cells,short rowIndex,HSSFSheet sheet)
    {
        HSSFRow row = sheet.createRow(rowIndex);
        for(int i = 0; i<cells.size();i++)
        {
            HSSFCell cell = row.createCell(i);
            CellType cellType = cell.getCellType();
            if(!StringUtils.equals(cellType.name(),CellType.STRING.name()))
            {
                cell.setCellType(CellType.STRING);
            }
            cell.setCellValue(cells.get(i));
            // 设置自动列宽
            sheet.autoSizeColumn(i);
            sheet.setColumnWidth(i,sheet.getColumnWidth(i)*17/10);
        }
    }

    private static void createExcelSheet(String[] tableNames, HSSFSheet sheet,List<ExcelDomain> list)
    {

        createTableHeader(tableNames,sheet);
        int rowIndex = 1;
        if(org.apache.commons.collections4.CollectionUtils.isNotEmpty(list))
        {
            for(int i=0;i<list.size();i++)
            {
                List<String> listRead = new ArrayList<>();

                listRead.add(list.get(i).getAge());
                listRead.add(list.get(i).getClazz());
                listRead.add(list.get(i).getGender());
                listRead.add(list.get(i).getName());
                listRead.add(list.get(i).getTitle());
                cereteTableRow(listRead,(short) rowIndex,sheet);
                rowIndex++;
            }
        }
    }

    public static InputStream exportExcelInputStream(HSSFSheet sheet,HSSFWorkbook workbook,ByteArrayOutputStream outputStream)
    {
        InputStream inputStream = null ;

        sheet.setGridsPrinted(true);
        HSSFFooter footer = sheet.getFooter();
        footer.setRight("Page"+HSSFFooter.page()+"of"+HSSFFooter.numPages());

        try {
            workbook.write(outputStream);
            inputStream = new ByteArrayInputStream(outputStream.toByteArray());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return inputStream ;
    }

    /**
     * 生成excel 输出流
     * @param workBookName
     * @param tableHeaderName
     * @param list
     * @return
     */
    public static InputStream produceExcelInputStream(String workBookName,String[] tableHeaderName,List<ExcelDomain> list)
    {
        InputStream inputStream = null ;
        HSSFWorkbook workbook = new HSSFWorkbook();
        ByteArrayOutputStream out = new ByteArrayOutputStream();

        HSSFSheet sheet = workbook.createSheet();
        createExcelSheet(tableHeaderName,sheet,list);

        inputStream = exportExcelInputStream(sheet,workbook,out);

        return inputStream ;
    }

    public static void produceExcel(String fileName,String[] tableHeaderName,List<ExcelDomain> list)
    {

        HSSFWorkbook workbook = new HSSFWorkbook();
        ByteArrayOutputStream out = new ByteArrayOutputStream();

        HSSFSheet sheet = workbook.createSheet();
        createExcelSheet(tableHeaderName,sheet,list);

        FileOutputStream fileOutputStream = null ;
        try {
            fileOutputStream = new FileOutputStream("./"+fileName);
            workbook.write(fileOutputStream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


    }

    // 设置表格样式
    private static HSSFCellStyle setHeadStyle(HSSFWorkbook workbook,boolean isHeader)
    {
        HSSFCellStyle cellStyle = workbook.createCellStyle();
        if(isHeader)
        {
            // 设置背景色
            cellStyle.setFillForegroundColor(HSSFColor.HSSFColorPredefined.LIGHT_GREEN.getIndex());
            //设置填充色
            cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
            //设置标题
            HSSFFont font = workbook.createFont();
            font.setFontHeightInPoints((short) 12);

            font.setFontName("宋体");
            //设置字体粗体
            font.setBold(true);
            cellStyle.setFont(font);
        }
        else
        {
            cellStyle.setFillForegroundColor(HSSFColor.HSSFColorPredefined.WHITE.getIndex());
        }
        //设置水平juzhong
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        //设置上下左右边框
        cellStyle.setBorderBottom(BorderStyle.THIN);
        cellStyle.setBorderLeft(BorderStyle.THIN);
        cellStyle.setBorderRight(BorderStyle.THIN);
        cellStyle.setBorderTop(BorderStyle.THIN);

        return cellStyle ;
    }

    public static void main(String[] args)
    {
        ArrayList<ExcelDomain> list = new ArrayList<>();
        for(int i = 0;i<500;i++)
        {
            ExcelDomain excelDomain = new ExcelDomain("小米" + i, i + "", "class" + i, "男", "title" + i);
            list.add(excelDomain);
        }

        Calendar cal = Calendar.getInstance();
        Date date = cal.getTime();

        System.out.println("HSSFWorkbook生成excel 开始时间"+new SimpleDateFormat("yyyy/MM/dd-HH:mm:ss:SSS").format(date));

        String fileName = "测试生成文件.xls";
        String[] names = {"姓名","年龄","班级","性别","title"};
        produceExcel(fileName,names,list);

        Calendar cal1 = Calendar.getInstance();
        Date date1 = cal1.getTime();
        System.out.println("HSSFWorkbook生成excel 截至时间"+new SimpleDateFormat("yyyy/MM/dd-HH:mm:ss:SSS").format(date1));
    }

}

说明:如果只需供前台下载,后台部分就没必要先生成excel,在读流在删除临时文件,可以直接使用com.lau.module.Utils.HssfExcelUtils#produceExcelInputStream该方法即可。

2. SXSSFWorkbook的使用
此工具类具有通用性,数据源数据只需是ArrayList list 类型即可,程序自动按照Object 字段的顺序依次解析,使数据一一对应。

package com.lau.module.Utils;

import com.lau.module.common.domain.ExcelDomain;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.*;
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.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
 * @ClassName SXSSFworkbookExcelUtil
 * @Description 处理海量数据生成excel工具类
 * @Author Mr.Lau
 * @Date 2020/05/31 12:31
 * @Version 1.0
 **/
public class SXSSFworkbookExcelUtil {

    private static CellStyle setCellStyle(SXSSFWorkbook wb,boolean isHeader)
    {
        CellStyle cellStyle = wb.createCellStyle();
        if(isHeader)
        {
            cellStyle.setFillForegroundColor(HSSFColor.HSSFColorPredefined.LIGHT_GREEN.getIndex());
            cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        }
        //水平居中
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        //设置上下左右边框
        cellStyle.setBorderTop(BorderStyle.THIN);
        cellStyle.setBorderBottom(BorderStyle.THIN);
        cellStyle.setBorderLeft(BorderStyle.THIN);
        cellStyle.setBorderRight(BorderStyle.THIN);

        //字体设置
        Font font = wb.createFont();
        font.setFontName("宋体");
        font.setBold(true);
        font.setFontHeightInPoints((short)11);

        cellStyle.setFont(font);
        //自动换行
        cellStyle.setWrapText(true);

        return cellStyle ;
    }

    private static void createExcelSheet(SXSSFWorkbook wb, List<Object> list,
                                         int eachSheetNum ,String[] firstRowName,int columnWidth)
    {
        // 创建头
        if(list !=null && list.size()>=0)
        {
            //获取分页指标
            int total = list.size();
            int avg = (total/eachSheetNum)+1;
            if(total %eachSheetNum == 0)
            {
                // sheet的数量
                avg = (total/eachSheetNum);
            }
            //样式 行首样式
            CellStyle headCellStyle = setCellStyle(wb, true);
            // 数据项样式
            CellStyle dataCellStyle1 = setCellStyle(wb, false);

            for(int m=0;m<avg ;m++)
            {
                SXSSFSheet sheet = wb.createSheet("报表导出" + (m + 1));
                sheet.createFreezePane(0,1,0,1);
                int rowNum = 0 ;
                // 设置列宽
                sheet = setSxssfSheet(sheet,headCellStyle,firstRowName,columnWidth,rowNum);
                rowNum += 1;

                //数据渲染

                Object excelDomain = null ;
                SXSSFRow sheetRow = null ;
                SXSSFCell sheetCell = null ;
                for(int n = eachSheetNum*m ;n<eachSheetNum*(m+1);n++)
                {
                    if(n<total)
                    {
                        excelDomain = list.get(n);
                        sheetRow = sheet.createRow(rowNum);
                        sheetRow.setHeight((short)400);
                        // 数据项赋值处理
                        handleData(sheetCell,sheetRow,dataCellStyle1,excelDomain,n,firstRowName.length);
                        rowNum +=1 ;
                        // help gc
                        excelDomain = null;
                        sheetCell = null ;
                        sheetRow = null ;
                    }

                }

            }
        }
    }

    private static SXSSFSheet setSxssfSheet(SXSSFSheet sheet,CellStyle cellStyle,
                                            String[] firstRowName,int columnWidth,int sheetRowNum)
    {
        if(firstRowName !=null && firstRowName.length !=0)
        {
            SXSSFRow sheetRow = null ;
            int headLength = firstRowName.length ;
            for(int i =0;i<headLength;i++)
            {
                // 设置列宽
                sheet.setColumnWidth(i,columnWidth);
                if(i== 0)
                {
                    sheetRow = sheet.createRow(sheetRowNum);
                    //设置行高
                    sheetRow.setHeight((short) 400);
                }
                SXSSFCell sheetCell = sheetRow.createCell(i);
                sheetCell.setCellValue(firstRowName[i]);
                sheetCell.setCellStyle(cellStyle);
            }
        }
        return sheet ;
    }

    /**
     * 列名的顺序需要和对应映射的数据类的属性顺序一致,确保数据一一对应,真实有效
     * @param sheetCell
     * @param sheetRow
     * @param style
     * @param obj
     * @param n
     * @param headLen
     */
    private static void handleData(SXSSFCell sheetCell,SXSSFRow sheetRow,
                                   CellStyle style,Object obj,int n,int headLen)
    {
        int i= 0 ;
        // 利用反射技术,获取类字段属性的值
        ArrayList<String> fieldList = ReflectUtils.getFieldList(obj);
        while(i !=headLen)
        {
            sheetCell = sheetRow.createCell(i);
            sheetCell.setCellStyle(style);
            String value = fieldList.get(i);
            sheetCell.setCellValue(value);
            i++ ;
        }
    }

    /**
     * 生成输出流,供前端下载使用
     * @param firstRowName 列名
     * @param list  待下载数据源
     * @param eachSheetNum  每张表展示数据的个数,
     * @param columnWidth  列宽
     * @return
     */
    public static InputStream produceExcelStream(String[] firstRowName,List<Object> list,int eachSheetNum,int columnWidth)
    {
        SXSSFWorkbook workbook = new SXSSFWorkbook(1000);
        ByteArrayOutputStream out = new ByteArrayOutputStream();

        InputStream inputStream = null ;

        createExcelSheet(workbook,list,eachSheetNum,firstRowName,columnWidth);

        try {
            workbook.write(out);
            inputStream = new ByteArrayInputStream(out.toByteArray());
            workbook.dispose();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(out !=null)
                {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return inputStream ;
    }


    /**
     * 生成excel文件   文件后缀名必须为 .xlsx
     * 要想生成文件后缀名为.xls文件,请使用HSSFWorkbook
     * @param fileName 文件名包括文件后缀
     * @param firstRowName
     * @param list
     * @param eachSheetNum
     * @param columnWidth
     */
    public static void produceExcel(String fileName,String[] firstRowName,List<Object> list,int eachSheetNum,int columnWidth)
    {
        SXSSFWorkbook workbook = new SXSSFWorkbook(1000);
        FileOutputStream fileOutputStream = null ;
        createExcelSheet(workbook,list,eachSheetNum,firstRowName,columnWidth);
        try {
            fileOutputStream = new FileOutputStream("./"+fileName);
            workbook.write(fileOutputStream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(fileOutputStream !=null)
                {
                    fileOutputStream.close();
                }
            } catch (IOException e) {
            }
        }

    }
}

其中利用反射技术处理代码如下:

package com.lau.module.Utils;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @ClassName ReflectUtils
 * @Description TODO
 * @Author l_zha
 * @Date 2020/05/31 13:21
 * @Version 1.0
 **/
public class ReflectUtils {

    /**
      * @Author lau
      * @Param obj 某个具体的对象
      * @return java.util.ArrayList
      **/
    public static ArrayList<String> getFieldList (Object obj)
    {
        ArrayList<String> list =null ;
        Field[] declaredFields = obj.getClass().getDeclaredFields();
        if(declaredFields !=null && declaredFields.length !=0){
            list =  new ArrayList<>() ;
            try {
                for (Field field : declaredFields) {
                    field.setAccessible(true);
                    String value = field.get(obj).toString();
                    list.add(value);
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return  list ;
    }


    public HashMap<String,String> getFieldMap (Object obj)
    {
        HashMap<String,String> map =null ;
        Field[] declaredFields = obj.getClass().getDeclaredFields();
        if(declaredFields !=null && declaredFields.length !=0){
            map =  new HashMap<>() ;
            try {
                for (Field field : declaredFields) {
                    //字段名称
                    String name = field.getName();
                    field.setAccessible(true);
                    Object o = field.get(obj);
                    map.put(name,o.toString());
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return  map ;
    }


    public static List<Map<String, Object>> getKeyAndValues(List<Object> objects) {
        return objects.stream().map(obj -> {
            Class clazz = obj.getClass();
            return Arrays.stream(clazz.getDeclaredFields()).collect(Collectors.toMap(Field::getName, field -> {
                Object resultObj = null;
                field.setAccessible(true);
                try {
                    resultObj = field.get(obj);
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                return Optional.ofNullable(resultObj).orElse(0);
            }, (k1, k2) -> k2));
        }).collect(Collectors.toList());
    }


}

以上,结合网上资源做的总结,并将部分做成通用性代码,欢迎转载。转载需要醒目标识原文地址

你可能感兴趣的:(报表处理)