1. 程式人生 > >Java POI實現excel大數據量下載

Java POI實現excel大數據量下載

spec member 數據量 system pac 空字符 ger ima bin

最近,在做一個數據導出的功能,需求描述如下:

當用戶在頁面點擊“搜索”,查詢符合條件的記錄,然後點擊導出,實現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大數據量下載