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