Java POI实现excel大数据量下载

Posted claindoc

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java POI实现excel大数据量下载相关的知识,希望对你有一定的参考价值。

最近,在做一个数据导出的功能,需求描述如下:

当用户在页面点击“搜索”,查询符合条件的记录,然后点击导出,实现excel文件下载,直接输出到浏览器,保存文件到本地。

需求分析

  1. 满足需求基本功能,考虑性能问题,对下载记录数可以控制。
  2. 大文件导出之前,进行压缩,用户导出文件压缩包之后,使用本地的解压工具可以解压。

以下是其代码实现记录,防止后续重复工作。

参考链接:http://poi.apache.org/components/spreadsheet/index.html

  1. 抽象excel文件内容
  2. 下载excel文件
  3. 压缩文件
  4. 直接输出到浏览器

POI实现Excel文件下载的接口为org.apache.poi.ss.usermodel.Workbook,它有三个实现,如下图:
技术分享图片

查阅官网,比较三者性能(性能对比图如下),最后决定选取org.apache.poi.xssf.streaming.SXSSFWorkbook.
技术分享图片


import java.util.List;
import java.util.Map;

/**
 * excel表格信息
 * 
 * @author xiaoqiang.guo.wb
 *
 */
public class ExcelShellProperty {
    
    /**
     * excel表头属性值
     */
    
    private String[] colProperties;
    
    /**
     * excel表格展示信息
     */
    private String[] colView;
    
    /**
     * excel sheet名称
     */
    private String sheetName;
    
    /**
     * excel 数据
     */
    private List<Map<String,Object>> dataList;
    
    /**
    * excel 附加信息  如总记录数等 
    */
    private String extMsg;
    
    
    public String[] getColProperties() {
        return colProperties;
    }
    public void setColProperties(String[] colProperties) {
        this.colProperties = colProperties;
    }
    public String[] getColView() {
        return colView;
    }
    public void setColView(String[] colView) {
        this.colView = colView;
    }
    public String getSheetName() {
        return sheetName;
    }
    public void setSheetName(String sheetName) {
        this.sheetName = sheetName;
    }
    public List<Map<String,Object>> getDataList() {
        return dataList;
    }
    public void setDataList(List<Map<String,Object>> dataList) {
        this.dataList = dataList;
    }
    public String getExtMsg() {
        return extMsg;
    }
    public void setExtMsg(String extMsg) {
        this.extMsg = extMsg;
    }
    
    
}

public class ExcelUtils2Xlsx {

    private static final Logger logger = LoggerFactory.getLogger(ExcelUtils2Xlsx.class);

    public File create(List<ExcelShellProperty> sheets,HttpServletResponse response, File fileDir,int n) {
        logger.info("@@ExcelUtils2Xlsx create excel文件  start");
        Long start = System.currentTimeMillis();
        SXSSFWorkbook book = null;
        File tempfile = null;
        FileOutputStream out = null;
        try {
            book = new SXSSFWorkbook(100);//keep 100 rows in memory, exceeding rows will be flushed to disk
            int sheetNo = 1;
            for (ExcelShellProperty excelShellProperty : sheets) {
                Sheet sheet = book.createSheet(excelShellProperty.getSheetName());
                
                //add
                Font headFont = book.createFont();
                headFont.setFontName("宋体");
                headFont.setColor(IndexedColors.BLUE.index);
                headFont.setBoldweight((short)12);

                //样式设置
                CellStyle cellStyle = book.createCellStyle();
                cellStyle.setFont(headFont);
                cellStyle.setAlignment(CellStyle.ALIGN_CENTER);
                cellStyle.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
                cellStyle.setFillBackgroundColor(IndexedColors.YELLOW.index);
                
                String[] colView = excelShellProperty.getColView();
                int i = 0;
                //生成表头信息
                Row row = sheet.createRow(0);
                for (int len = colView.length; i < len; ++i) {
                    sheet.setColumnWidth(i, 3766);
                    Cell cell = row.createCell(i);
                    cell.setCellValue(colView[i]);
                    cell.setCellStyle(cellStyle);   
                }

                setExcelBody(excelShellProperty.getDataList(),excelShellProperty.getColProperties(),excelShellProperty.getExtMsg(),colView.length, sheet,headFont,cellStyle);
                ++sheetNo;
            }
            
            // 将流信息转换为文件流 创建文件流
            String tempFileName = createTempFileName(n);
            tempfile = new File(fileDir + "//" + tempFileName);
            logger.info("@@ExcelUtils2Xlsx 文件路径" + tempfile.getAbsolutePath()+ ";文件名称为" + tempfile.getName());
            out = new FileOutputStream(tempfile);
            
            book.write(out);
            out.flush();
            book.dispose();
            
            Long end = System.currentTimeMillis();
            logger.info("@@ExcelUtils2Xlsx create excel文件  end,生成excel文件用时 "+(end-start)+"毫秒");
            return tempfile;
        } catch (Exception e) {
            logger.info("@@ExcelUtils2Xlsx occur exception",e);
        } finally {
            try {
                if(out != null){
                    IOUtils.closeQuietly(out);
                }
            } catch (Exception e2) {
                logger.error("@@ExcelUtils2Xlsx downLoad occur exception",e2);
            }
        }
        return null;
    }
    
    /**
     * 生成excel文件名称
     * 
     * @param i
     * @return
     */
    private String createTempFileName(int i) {
        
        String tempFileName = "指令查询结果"+i+".xlsx";
        return tempFileName;
    }

    /**
     * 表格内容设置 注意需要分页信息
     * 
     * @param massList
     * @param colProperties
     * @param extMsg
     * @param label
     * @param sheet1
     * @throws WriteException
     */
    public static void setExcelBody(List<Map<String,Object>> massList, String[] colProperties,String extMsg,int length,Sheet sheet, Font bodyFont,CellStyle cellStyle )throws Exception {
        
        //设置字体信息
        bodyFont.setFontName("宋体");
        bodyFont.setBoldweight((short)10);
        bodyFont.setColor(IndexedColors.BLACK.index);
        
        //设置单元格格式
        cellStyle.setAlignment(CellStyle.ALIGN_CENTER);
        cellStyle.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
        
        int i = 0;
        for (int len = massList.size(); i < len; ++i) {
            Row row = sheet.createRow(i+1);
            
            Map<String,Object> listOrderMap = massList.get(i);
            
            int j = 0;
            for (int lenth2 = colProperties.length;j < lenth2; ++j) {
                Cell cell = row.createCell(j);
                cell.setCellStyle(cellStyle);
                
                String cellV = listOrderMap.get(colProperties[j]) == null ? "": (String) listOrderMap.get(colProperties[j]);
                cell.setCellValue(cellV);
            }
        }
        //额外信息
        if(StringUtils.isNotEmpty(extMsg)){
            
            int lastRowNum = sheet.getLastRowNum();
            Row row = sheet.createRow(lastRowNum+1);

            Cell createCell = row.createCell(0);
            createCell.setCellStyle(cellStyle);
            createCell.setCellValue(extMsg);
        }
        
    }
    
    
    
}
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * 查询导出处理类
 * 
 * @author xiaoqiang.guo.wb
 *
 */
public class ExcelInfoDownloadUtils {
    
    private static final Logger logger = LoggerFactory.getLogger(ExcelInfoDownloadUtils.class);
    
    /**
     * 表头展示信息
     */
    private static final String[] ExcelInfoViews = {"批次号","交易号","商户编号","商户名称","业务类型","反馈信息",
        "付款银行账号","付款账户名称", "收款银行账号","收款账户名称","币种", "金额","渠道名称", "指令状态","创建日期","修改日期","指令发送时间"};
    
    /**
     * 表头展示信息对应属性
     */
     private static final String[] ExcelInfoProperties = {"batchIdStr","orderSeqIdStr","memberCodeStr","memberName","isIndvidualStr","bankRemind",
            "payerAcctNo","payerAcctName","payeeAcctNo","payeeAcctName","payeeCurrencyStr","amountStr","channelName","statusStr","crtTimeStr","updTimeStr","sendTimeStr"};
    
    /**
     * 生成excel文件
     * 
     * @param dataList
     * @param response
     * @param fileDir
     * @param i
     * @param extMsg
     * @return
     */
    public File createFile(List<Map<String,Object>> dataList,HttpServletResponse response,File fileDir,int i,String extMsg){
            
        logger.info("@@BankInstrInfoDownloadHelper 下载指令查询结果list size"+dataList.size());
        //1.设置表格输出元信息
        List<ExcelShellProperty> downList = new ArrayList<ExcelShellProperty>();
        
        ExcelShellProperty excelShellProperty = new ExcelShellProperty();
        excelShellProperty.setColProperties(BankInstrInfoProperties);
        excelShellProperty.setColView(BankInstrInfoViews);
        excelShellProperty.setDataList(dataList);
        excelShellProperty.setSheetName("指令查询");
        excelShellProperty.setExtMsg(extMsg);
        
        downList.add(excelShellProperty);
        
        ExcelUtils2Xlsx excelHelper = new ExcelUtils2Xlsx();
        File file = excelHelper.create(downList,response,fileDir,i);
        
        return file;
    }
}


实现导出数据之前,需要对导出数据的格式进行预处理,并将其转换为map格式。

以下是实现Zip压缩的实现:

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * zip文件处理工具类
 * 
 * @author xiaoqiang.guo.wb
 * 
 */
public class ZipUtils {

    private static final Log log = LogFactory.getLog(ZipUtils.class);

    /**
     * 压缩文件
     * 
     * @param srcfile
     *            File[] 需要压缩的文件列表
     * @param zipfile
     *            File 压缩后的文件
     */
    public static void zipFiles(List<File> srcfile, File zipfile) {
        
        ZipOutputStream out = null;
        try {
            out = new ZipOutputStream(new FileOutputStream(zipfile));
            for (int i = 0; i < srcfile.size(); i++) {
                File file = srcfile.get(i);
                FileInputStream in = new FileInputStream(file);
                out.putNextEntry(new ZipEntry(file.getName()));
                byte[] buf = new byte[1024];
                int len;
                while ((len = in.read(buf)) > 0) {
                    out.write(buf, 0, len);
                }
                out.closeEntry();
                IOUtils.closeQuietly(in);
            }
            // Complete the ZIP file
            //out.close();
        } catch (IOException e) {
            log.error("@@ZipUtils zipFiles exception:" , e);
        }finally{
            if(out != null){
                IOUtils.closeQuietly(out);
            }
        }
    }

    /**
     * 解压缩
     * 
     * @param zipfile
     *            File 需要解压缩的文件
     * @param descDir
     *            String 解压后的目标目录
     */
    public static void unZipFiles(File zipfile, String descDir) {
        InputStream in = null ;
        OutputStream out = null;
        try {
            // Open the ZIP file
            ZipFile zf = new ZipFile(zipfile);
            for (Enumeration entries = zf.entries(); entries.hasMoreElements();) {
                // Get the entry name
                ZipEntry entry = ((ZipEntry) entries.nextElement());
                String zipEntryName = entry.getName();
                in = zf.getInputStream(entry);
                out = new FileOutputStream(descDir + zipEntryName);
                byte[] buf1 = new byte[1024];
                int len;
                while ((len = in.read(buf1)) > 0) {
                    out.write(buf1, 0, len);
                }
                // Close the file and stream
                out.flush();
                //in.close();
                //out.close();
            }
        } catch (IOException e) {
            log.error("ZipUtils unZipFiles exception:",e);
        }finally{
            if(in != null){
                IOUtils.closeQuietly(in);
            }
            if(out != null){
                IOUtils.closeQuietly(out);
            }
        }
    }

    public static void downFile(HttpServletResponse response,
            String serverPath, String fileName) {
        
        InputStream ins = null;
        BufferedInputStream bins = null;
        OutputStream outs = null;
        BufferedOutputStream bouts = null;
        try {
            String path = serverPath + "//"+fileName;
            File file = new File(path);
            if (file.exists()) {
                ins = new FileInputStream(path);
                bins = new BufferedInputStream(ins);// 放到缓冲流里面
                outs = response.getOutputStream();// 获取文件输出IO流
                bouts = new BufferedOutputStream(outs);
                response.setContentType("application/x-download");// 设置response内容的类型
                response.setHeader("Content-disposition","attachment;filename="+ URLEncoder.encode(fileName, "UTF-8"));// 设置头部信息
                int bytesRead = 0;
                byte[] buffer = new byte[8192];
                // 开始向网络传输文件流
                while ((bytesRead = bins.read(buffer, 0, 8192)) != -1) {
                    bouts.write(buffer, 0, bytesRead);
                }
                bouts.flush();// 这里一定要调用flush()方法 将缓存中的数据写刷新到目标源
                //ins.close();
                //bins.close();
                //outs.close();
                //bouts.close();
            } else {
                log.info("zip文件输出时,未找到相关文件位置信息");
            }
        } catch (IOException e) {
            log.info("@@ZipUtils downFile  error" ,e);
        } finally {
            if(ins != null){
                IOUtils.closeQuietly(ins);
            }
            if(bins != null){
                IOUtils.closeQuietly(bins);         
            }
            if(outs != null){
                IOUtils.closeQuietly(outs);
            }
            if(bouts != null){
                 IOUtils.closeQuietly(bouts);
            }
        }

    }

    /**
     * 递归删除文件夹
     * 
     * @param path
     * @return
     */
    public static boolean delete(String path) {
        File file = new File(path);
        boolean success = false;
        if (file.isDirectory()) {
            String[] list = file.list(); // 返回该目录下的所有文件的文件名称(注意,只显示直属目录下的信息)
            for (int i = 0; i < list.length; i++) {
                success = delete(path + "//"+ list[i]); // 注意该处的分隔符号
            }
        } else {
            success = file.delete();
        }

        return success;
    }

}

最后,附上bean转map的实现。

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

/**
 * bean to map 转换工具类
 * @author xiaoqiang.guo.wb
 *
 */
public class BeanUtils {

    /**
     * 空字符串
     */
    static final String emptyString = "";
    
    private final static Logger log = LoggerFactory.getLogger(BeanUtils.class);
    /**
     * Map中根据key获得字符串
     * 
     * @param params 容器
     * @param key key值
     * @return 字符串类型的value
     */
    public static <K, V> String getString(Map<K, V> params, K key)
    {
        if (CollectionUtils.isEmpty(params))
        {
            return emptyString;
        }
        V value = params.get(key);
        return null == value ? emptyString : value.toString();
    }
    
    
    /**
     * bean转 Map
     * @param bean
     * @return Map
     */
    public static Map<String, Object> bean2Map(Object javaBean) {
        Map<String, Object> map = new HashMap<String, Object>();
        try {
            // 获取javaBean属性
            BeanInfo beanInfo = Introspector.getBeanInfo(javaBean.getClass());

            PropertyDescriptor[] propertyDescriptors = beanInfo
                    .getPropertyDescriptors();
            if (propertyDescriptors != null && propertyDescriptors.length > 0) {
                String propertyName = null; // javaBean属性名
                Object propertyValue = null; // javaBean属性值
                for (PropertyDescriptor pd : propertyDescriptors) {
                    propertyName = pd.getName();
                    if (!propertyName.equals("class")) {
                        Method readMethod = pd.getReadMethod();
                        propertyValue = readMethod.invoke(javaBean,
                                new Object[0]);
                        map.put(propertyName, propertyValue);
                    }
                }
            }
        } catch (Exception e) {
            log.error("bean to map error");
        }
        return map;
    }
      
}

(完)





以上是关于Java POI实现excel大数据量下载的主要内容,如果未能解决你的问题,请参考以下文章

怎么使用java Poi解决导入excel表格大数据量时的内存溢出问

java excel poi 大数据量50W 内存溢出

POI读写大数据量EXCEL

用java的poi类读取一个excel表格的内容后再写入到一个新excel表格中的完整代码

poi导出大数据,报内存溢出怎么解决

POI 生成excel(大数据量) SXSSF