1. 程式人生 > >POI 實現 Excel 檔案上傳下載及大資料匯出處理

POI 實現 Excel 檔案上傳下載及大資料匯出處理

Java 中操作 Excel 的有兩種比較主流的工具包: JXL 和 POI 。JXL 只能操作 Excel 95、97、2000 等老版本格式資料,也即以 .xls 為字尾的 excel。而 POI 可以操作 Excel 95 及以後的版本,即可操作字尾為 .xls 和 .xlsx 兩種格式的 Excel。

POI 全稱 Poor Obfuscation Implementation,利用 POI 介面可以通過 Java 操作 Microsoft Office 套件工具的讀寫功能,POI 支援 Office 的所有版本。

1. Excel 檔案上傳與下載

本節是對 Excel 檔案的下載和上傳實現;其在 CRM 系統中比較常見,即需要實現下載和上傳表格資料資訊,總的來說下載是比較容易實現,上傳由於格式必須與資料庫欄位對應,顯得有些麻煩;該 demo 實驗為了方便期間,沒有與資料庫進行互動,使用的是 Servlet 實現;

依賴包如下:

  • poi.jar(Java 操作檔案 API 相關)
  • commons-io.jar(檔案流相關)
  • commons-fileupload.jar(檔案傳輸相關)

jar 包官網下載地址如下:

程式碼實現如下:

web.xml 配置檔案:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation
="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet> <servlet-name>PoiServlet</servlet-name> <servlet-class>cn.smart4j.controller.PoiServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name
>
PoiServlet</servlet-name> <url-pattern>/poiTest.jsp</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>

JSP 頁面 index.jsp:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>poi-Demo</title>
  </head>
  <body>
    <a href="poiTest.jsp?cmd=downFile">下載</a><br>
    <form action="poiTest.jsp?cmd=uploadFile" method="post" enctype="multipart/form-data">
        <input type="file" name="file">
        <input type="submit" value="submit">
    </form>
  </body>
</html>

controller 層程式碼實現:

PoiServlet.java 程式碼如下:

package cn.smart4j.controller;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;

public class PoiServlet extends HttpServlet {    
    private static final long serialVersionUID = 1L;  

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String methodName = request.getParameter("cmd");        
        if("downFile".equals(methodName)){
            downFile(request,response);
        }else if("uploadFile".equals(methodName)){
            uploadFile(request,response);
        }

    }    

    private void uploadFile(HttpServletRequest request,
            HttpServletResponse response) {        
        if(ServletFileUpload.isMultipartContent(request)){
            DiskFileItemFactory factory = new DiskFileItemFactory();
            factory.setSizeThreshold(1024*512);
            factory.setRepository(new File("D:/tempFile"));
            ServletFileUpload fileUpload=new ServletFileUpload(factory);
            fileUpload.setFileSizeMax(10*1024*1024);//設定最大檔案大小
            try {                
                @SuppressWarnings("unchecked")
                List<FileItem> items=fileUpload.parseRequest(request);//獲取所有表單
                for(FileItem item:items){                    
                    //判斷當前的表單控制元件是否是一個普通控制元件
                    if(!item.isFormField()){                        
                        //是一個檔案控制元件時
                        String excelFileName = new String(item.getName().getBytes(), "utf-8"); //獲取上傳檔案的名稱
                        //上傳檔案必須為excel型別,根據字尾判斷(xls)
                        String excelContentType = excelFileName.substring(excelFileName.lastIndexOf(".")); //獲取上傳檔案的型別                 
                        if(".xls".equals(excelContentType)){
                            POIFSFileSystem fileSystem = new POIFSFileSystem(item.getInputStream());
                            HSSFWorkbook workbook = new HSSFWorkbook(fileSystem);
                            HSSFSheet sheet = workbook.getSheetAt(0);                            
                            int rows = sheet.getPhysicalNumberOfRows();                           
                            for (int i = 0; i < rows; i++) {
                                HSSFRow row = sheet.getRow(i);                               
                                int columns = row.getPhysicalNumberOfCells();                               
                                for (int j = 0; j < columns; j++) {
                                    HSSFCell cell = row.getCell(j);
                                    String value = this.getCellStringValue(cell);
                                    System.out.print(value + "|");
                                }
                            }
                        }else{
                            System.out.println("必須為excel型別");
                        }                        
                        response.sendRedirect("index.jsp");
                    }
                }
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }    

    private void downFile(HttpServletRequest request,
            HttpServletResponse response) {
        response.setContentType("application/vnd.ms-excel;charset=UTF-8");
        response.setHeader("Content-Disposition", "attachment;filename=data.xls"); 

        ServletOutputStream stream = null;        
        try {
            stream = response.getOutputStream();
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        HSSFWorkbook workbook = new HSSFWorkbook();
        HSSFCellStyle style = workbook.createCellStyle();
        style.setAlignment(HSSFCellStyle.ALIGN_CENTER);//左右居中樣式

        HSSFSheet sheet = workbook.createSheet("sheetName");
        sheet.setColumnWidth(0, 2000);
        sheet.setColumnWidth(1, 5000);        
        //建立表頭(第一行)
        HSSFRow row = sheet.createRow(0);        
        //列
        HSSFCell cell = row.createCell(0);
        cell.setCellValue("姓名");
        cell.setCellStyle(style);
        HSSFCell cell2 = row.createCell(1);
        cell2.setCellValue("年齡");
        cell2.setCellStyle(style);        
        //建立資料行
        for(int i =1;i<=20;i++) {
            HSSFRow newRow = sheet.createRow(i);
            newRow.createCell(0).setCellValue("smart"+i);
            newRow.createCell(1).setCellValue(i);
        }        try {
            workbook.write(stream);
            stream.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(stream != null) {
                stream.close();
            }
        }
    }    

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }    

    //轉換單元格資料型別
    public String getCellStringValue(HSSFCell cell) {   
        String cellValue = "";   
        switch (cell.getCellType()) {   
        case HSSFCell.CELL_TYPE_STRING:   
            cellValue = cell.getStringCellValue();   
            if(cellValue.trim().equals("")||cellValue.trim().length()<=0)   
                cellValue=" ";   
            break;   
        case HSSFCell.CELL_TYPE_NUMERIC:   
            cellValue = String.valueOf(cell.getNumericCellValue());   
            break;   
        case HSSFCell.CELL_TYPE_FORMULA:   
            cell.setCellType(HSSFCell.CELL_TYPE_NUMERIC);   
            cellValue = String.valueOf(cell.getNumericCellValue());   
            break;   
        case HSSFCell.CELL_TYPE_BLANK:   
            cellValue=" ";   
            break;   
        case HSSFCell.CELL_TYPE_BOOLEAN:   
            break;   
        case HSSFCell.CELL_TYPE_ERROR:   
            break;   
        default:   
            break;   
        }   
        return cellValue;   
    }
}

2. 大資料匯出解決方案

POI 之前的版本不支援大資料量處理,如果資料過多則經常報 OOM 錯誤,有時候調整 JVM 大小效果也不是太好。3.8 版本的 POI 新出來了 SXSSFWorkbook,可以支援大資料量的操作,只是 SXSSFWorkbook 只支援 .xlsx 格式,不支援 .xls 格式。

3.8 版本的 POI 對 Excel 的匯出操作,一般只使用 HSSFWorkbook 以及 SXSSFWorkbook,HSSFWorkbook 用來處理較少的資料量,SXSSFWorkbook 用來處理大資料量以及超大資料量的匯出。

程式碼實現如下所示:

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
public class SXSSFTest {    
    public static void main(String[] args) throws IOException {        
        // 建立基於stream的工作薄物件的
        SXSSFWorkbook wb = new SXSSFWorkbook(100); 
        Sheet sh = wb.createSheet();        
        // 使用createRow將資訊寫在記憶體中。
        for (int rownum = 0; rownum < 1000; rownum++) {
            Row row = sh.createRow(rownum);            
            for (int cellnum = 0; cellnum < 10; cellnum++) {
                Cell cell = row.createCell(cellnum);
                String address = new CellReference(cell).formatAsString();
                cell.setCellValue(address);
            }

        }        
        // 當使用getRow方法訪問的時候,將記憶體中的資訊重新整理到硬碟中去。
        for (int rownum = 0; rownum < 900; rownum++) {
            System.out.println(sh.getRow(rownum));
        }
        for (int rownum = 900; rownum < 1000; rownum++) {
            System.out.println(sh.getRow(rownum));
        }        
        // 寫入檔案中
        FileOutputStream fos = new FileOutputStream("e://temp.xlsx");
        wb.write(fos);        
        // 關閉檔案流物件
        fos.close();
        System.out.println("基於流寫入執行完畢!");
    }
}

在此基礎上再優化的方案是匯出的 Excel 表格生成多個工作表即生成多個 sheet。

程式碼實現如下:

import java.io.IOException;import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;import java.util.Date;
import java.util.LinkedHashMap;import java.util.List;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import com.common.DateFormatUtil;public class ExlUtil {    
/**
     * @param excelHeader  表頭資訊
     * @param list  要匯出到excel的資料來源,List型別
     * @param sheetName  表名
     * @return
     */
    public static ResponseEntity<byte[]> getDataStream(ExcelHeader excelHeader,
            List list, String sheetName) {
        LinkedHashMap<String, List> map = new LinkedHashMap<String, List>();
        List<String[]> headNames = new ArrayList<String[]>();
        List<String[]> fieldNames = new ArrayList<String[]>();
        String[] sheetNames = new String[100];        
        //處理Excel生成多個工作表 
        //定義為每個工作表資料為50000條
        if (list.size() > 50000) {            
            int k = (list.size() + 50000) / 50000;            
            for (int i = 1; i <= k; i++) {                
                if (i < k) {
                    map.put(sheetName + i,
                            list.subList((i - 1) * 50000, i * 50000));
                } else {
                    map.put(sheetName + i,
                            list.subList((i - 1) * 50000, list.size()));
                }

                headNames.add(excelHeader.getHeadNames().get(0));
                fieldNames.add(excelHeader.getFieldNames().get(0));
                sheetNames[i - 1] = sheetName;
            }

        } else {
            map.put(sheetName, list);
            headNames.add(excelHeader.getHeadNames().get(0));
            fieldNames.add(excelHeader.getFieldNames().get(0));
            sheetNames[0] = sheetName;
        } 

        byte[] buffer = null;   

        try {
            buffer = ExcelUtil2.output(headNames, fieldNames, sheetNames, map);

        } catch (IllegalArgumentException | IllegalAccessException
                | IOException e) {
            e.printStackTrace();
        }
        HttpHeaders headers = new HttpHeaders();                           headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);        
        try {
            sheetName = new String(sheetName.getBytes("gbk"), "iso-8859-1");
        } catch (UnsupportedEncodingException e) {            
            e.printStackTrace();
        }
        String fileGenerateTime = DateFormatUtil.toStr(new Date());
        headers.setContentDispositionFormData("attachment", sheetName
                + fileGenerateTime + ".xlsx");        
        return new ResponseEntity<byte[]>(buffer, headers, HttpStatus.CREATED);
    };
}