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);
};
}