JAVA导出大数据量Excel文件的解决方案

最近在工作遇到了导出大数据量(10万级)Excel文件的问题,在网上找了很多文章都感觉不是很好,总内存溢出或卡死掉,偶尔能成功但很不稳定。

通过实践总结一套可行的解决方案,速度大约1000/s;

不管是使用POI、JXL还是FastExcel一次直接导出20万条数据性能暂不提就内存就受不了,这也是导致导出数据失败的主要原因,故使用多次导出每次可以限定在10000条数据(经测试是性能和稳定性最好-在普通配置得PC机上),然后将多个Excel文件压缩成一个ZIP文件提供前台下载。这需要提醒的是每次导出时都创建一个Excel文件而不是创建一个SHEET这样可以及时释放内存。

下面是具体实现

ExportExcelUtil

package com.quanyou.pwm.util;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.log4j.Logger;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

public class ExportExcelUtil {

	private static final Logger logger = org.apache.log4j.Logger
			.getLogger(ExportExcelUtil.class);
	private String fileName;// 导出excel名称
	private HttpServletRequest request;// 请求对象
	private int pageSize;// 分页大小
	private Object service;// 服务
	private Map<String, Object> params;// 参数
	private String method;// 调用服务方法名称
	private List<String> subFileNames;// 子文件集合
	private File zip;// 压缩文件
	private int start;// 开始条数
	private int fileIndex;// 子文件索引
	private List<Map<String, Object>> heads; // excel头部
	private String xmlHeadId; // 对应xml模版的配置
	private Map<String, Object> xml = ParseExport.getXml(); // 导出Excel模版Xml

	public static ExportExcelUtil getInstance(String fileName,
			HttpServletRequest request, int pageSize, Object service,
			Map<String, Object> params, String method, String xmlHeadId) {
		ExportExcelUtil instance = new ExportExcelUtil(fileName, request,
				pageSize, service, params, method, xmlHeadId);
		return instance;
	}

	public ExportExcelUtil() {
		init();
	}

	public ExportExcelUtil(String fileName, HttpServletRequest request,
			int pageSize, Object service, Map<String, Object> params,
			String method, String xmlHeadId) {
		this.fileName = fileName;
		this.request = request;
		this.pageSize = pageSize;
		this.service = service;
		this.params = params;
		this.method = method;
		this.xmlHeadId = xmlHeadId;
		init();
	}

	/**
	 * 导出Excel文件
	 */
	public String export() {
		FileOutputStream out = null;
		try {
			List<Object> list = null;
			do {
				Page page = new Page(start, pageSize);
				start += pageSize;
				list = getDatas(page);
				if (list != null && list.size() > 0) {
					String file = getExcelName(fileName, fileIndex++);
					subFileNames.add(file);
					out = new FileOutputStream(file);
					HSSFWorkbook workbook = createWorkbook(list);
					workbook.write(out);
					out.flush();
					try {
						out.close();
					} catch (Exception e) {
					}
				}
			} while (list != null && list.size() == pageSize);
			// 压缩文件
			compressFile();
		} catch (Exception e) {
			e.printStackTrace();
		}

		return zip.getName();
	}

	public HSSFWorkbook createWorkbook(List<Object> list) {
		HSSFWorkbook workbook = new HSSFWorkbook();
		try {
			HSSFSheet sheet = workbook.createSheet(fileName);

			// 创建第一行及excel的头部
			createExcelHead(sheet);

			for (int i = 0; i < list.size(); i++) {
				createExcelRow(list.get(i), sheet, i);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		return workbook;
	}

	// 创建excel头部
	private void createExcelHead(HSSFSheet sheet) {
		HSSFRow contenRow = sheet.createRow(0);
		HSSFCell contentCell = null;
		if (heads != null) {
			for (int i = 0; i < heads.size(); i++) {
				contentCell = contenRow.createCell(i);
				contentCell.setCellValue(heads.get(i).get("head").toString());
				sheet.setColumnWidth(i,
						Integer.parseInt(heads.get(i).get("width").toString()));
			}
		}
	}

	// 创建excel boy
	private void createExcelRow(Object bean, HSSFSheet sheet, int index) {
		HSSFRow contenRow = null;
		contenRow = sheet.createRow(index + 1);
		HSSFCell contentCell = null;
		for (int i = 0; i < heads.size(); i++) {
			contentCell = contenRow.createCell(i);
			contentCell.setCellValue(processValue(bean,
					heads.get(i).get("fields").toString()));
		}
	}

	// 处理每一个表格的数据
	private String processValue(Object bean, String field) {
		String result = "";
		try {
			Map<String, Object> map = BeanUtils.describe(bean);

			if (!"".equals(field)) {
				String[] fs = field.split(",");// 每一列的数据可能由多个字段组成用","解析
				for (String f : fs)
					result += map.get(f) + " ";
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result.trim();
	}

	// 得到要导出的数据
	private List<Object> getDatas(Page page) {
		List<Object> result = new ArrayList<Object>();
		try {
			result = (List<Object>) service.getClass()
					.getMethod(method, new Class[] { Map.class, Page.class })
					.invoke(service, new Object[] { params, page });
		} catch (Exception e) {
			e.printStackTrace();
		}

		return result;
	}

	// 压缩文件
	private void compressFile() {
		File[] srcfile = new File[subFileNames.size()];
		for (int i = 0; i < srcfile.length; i++) {
			srcfile[i] = new File(subFileNames.get(i));
		}
		FileZipUtil.ZipFiles(srcfile, zip);
		// 压缩完成所删除子文件
		for (String f : subFileNames) {
			File subFile = new File(f);
			if (subFile.exists()) {
				if (!subFile.delete())
					logger.error("清除子文件:" + subFile.getName() + " 失败");
			}
		}
	}

	// 创建数据流
	@Deprecated
	public ByteArrayInputStream createStream() {
		ByteArrayInputStream inputStream = null;
		BufferedInputStream in = null;
		ByteArrayOutputStream out = null;
		try {
			in = new BufferedInputStream(new FileInputStream(zip));
			out = new ByteArrayOutputStream(1048576);

			byte[] temp = new byte[1048576];
			int size = 0;
			while ((size = in.read(temp)) != -1) {
				out.write(temp, 0, size);
			}
			byte[] b = out.toByteArray();
			inputStream = new ByteArrayInputStream(b);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				in.close();
			} catch (Exception e) {
			}
			try {
				out.close();
			} catch (Exception e) {
			}
		}

		return inputStream;
	}

	private void init() {
		subFileNames = new ArrayList<String>();
		if (!"".equals(fileName))
			zip = new File(getZipName(fileName));
		start = 0;
		fileIndex = 1;
		loadHeads();
	}

	// 加载模版
	private void loadHeads() {
		if (xml != null && xml.get(xmlHeadId) != null) {
			this.heads = (List) xml.get(xmlHeadId);
		} else {
			logger.error("获取模版配置失败,将导致导出数据失败!");
		}
	}

	private String getZipName(String name) {
		return request.getSession().getServletContext().getRealPath("/")
				+ "/excels/" + System.currentTimeMillis() + ".zip";
	}

	private String getExcelName(String name, int index) {
		File root = new File(request.getSession().getServletContext()
				.getRealPath("/")
				+ "/excels");
		if (!root.exists())
			root.mkdir();
		return request.getSession().getServletContext().getRealPath("/")
				+ "/excels/" + name + "_" + DateFormat.DateToyyyy_MM_dd() + "_"
				+ System.currentTimeMillis() + "(" + index + ").xls";
	}

}

/**
 * 解析导出Excel文件模版
 * 
 * @author TangYang
 */
final class ParseExport {
	private final static String defaultXml = "init.xml";
	private final static String defaultPackage = "excel";
	private static String initConfig = "";
	private static String configRoot = "";
	private static Map<String, Object> xml = new HashMap<String, Object>();
	private static List<String> configFile = new ArrayList<String>();
	private static List<String> includeFile = new ArrayList<String>();

	public static Map<String, Object> getXml() {
		return xml;
	}

	static {
		initConfigPath();
		if (!"".equals(initConfig)) {
			configFile.add(initConfig);
			begin();
		}
	}

	// 开始解析xml文件
	private static void begin() {
		includeFile.clear();
		if (configFile != null) {
			for (String c : configFile) {
				File file = new File(c);
				if (file.exists())
					readXml(file);
			}
		}
		if (includeFile.size() > 0) {
			configFile.clear();
			configFile.addAll(includeFile);
			begin();
		}
	}

	// 读取配置文件
	private static void readXml(File file) {
		FileInputStream in = null;
		try {
			SAXReader reader = new SAXReader();
			in = new FileInputStream(file);
			Document doc = reader.read(in);
			Element root = doc.getRootElement();
			parseInclude(root);
			parseExcel(root);
		} catch (Exception e1) {
			e1.printStackTrace();
		} finally {
			try {
				if (in != null)
					in.close();
			} catch (Exception e) {
			}
		}
	}

	// 解析include的文件
	private static void parseInclude(Element root) {
		for (Iterator it = root.elementIterator(); it.hasNext();) {
			Element e = (Element) it.next();
			if ("include".equals(e.getName())) {
				String resouce = e.attributeValue("resouce");
				if (resouce != null && !"".equals(resouce))
					includeFile.add(configRoot + "/" + resouce);
			}
		}
	}

	// 解析Excel模版
	private static void parseExcel(Element root) {
		for (Iterator it = root.elementIterator(); it.hasNext();) {
			Element e = (Element) it.next();
			String key = e.attributeValue("id");
			xml.put(key, parseLine(e));
		}
	}

	private static List parseLine(Element node) {
		List result = new ArrayList();
		for (Iterator it = node.elementIterator(); it.hasNext();) {
			Element e = (Element) it.next();
			result.add(parseAttribte(e));
		}
		return result;
	}

	private static Map<String, Object> parseAttribte(Element node) {
		Map<String, Object> result = new HashMap<String, Object>();
		for (Iterator it = node.elementIterator(); it.hasNext();) {
			Element e = (Element) it.next();
			String key = e.attributeValue("name");
			if (key != null && !"".equals(key)) {
				result.put(key, e.getTextTrim());
			}
		}

		return result;
	}

	private static void initConfigPath() {
		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		Enumeration<URL> urls;

		try {
			urls = loader.getResources(defaultPackage.replace('.', '/'));
			while (urls.hasMoreElements()) {
				String urlPath = urls.nextElement().getFile();
				urlPath = URLDecoder.decode(urlPath, "UTF-8");
				// If it's a file in a directory, trim the stupid file: spec
				if (urlPath.startsWith("file:"))
					urlPath = urlPath.substring(5);

				// Else it's in a JAR, grab the path to the jar
				if (urlPath.indexOf('!') > 0)
					urlPath = urlPath.substring(0, urlPath.indexOf('!'));
				findFile(urlPath);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// 查找配置文件
	private static void findFile(String urlPath) {
		File file = new File(urlPath);
		if (file.exists()) {
			File[] files = file.listFiles();
			if (files != null && files.length > 0) {
				for (File f : files) {
					if (f.getName().equals(defaultXml)) {
						configRoot = urlPath;
						initConfig = urlPath + "/" + f.getName();
					}
				}
			}
		}
	}

}

 

FileZipUtil压缩文件工具类

package com.quanyou.pwm.util;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipOutputStream;
//import java.util.zip.ZipEntry;
//import java.util.zip.ZipOutputStream;

/**
 * 
 * @author http://javaflex.iteye.com/
 * 
 */
public class FileZipUtil {
	/**
	 * 
	 * @param srcfile
	 *            文件名数组
	 * @param zipfile
	 *            压缩后文件
	 */
	public static void ZipFiles(java.io.File[] srcfile, java.io.File zipfile) {
		byte[] buf = new byte[1024];
		try {
			ZipOutputStream out = new ZipOutputStream(new FileOutputStream(
					zipfile));
			for (int i = 0; i < srcfile.length; i++) {
				FileInputStream in = new FileInputStream(srcfile[i]);
				out.putNextEntry(new ZipEntry(srcfile[i].getName()));
				int len;
				while ((len = in.read(buf)) > 0) {
					out.write(buf, 0, len);
				}
				out.closeEntry();
				in.close();
			}
			out.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}


涉及到xml配置文件仅供参考

init.xml

<?xml version="1.0" encoding="UTF-8"?>
<export>
	<include resouce="wages-model.xml"></include>
</export>

wages-model.xml

<?xml version="1.0" encoding="UTF-8"?>
<export>
	<excel id="wages">
		<line>
			<property name="head">排工单号</property>
			<property name="width">4000</property>
			<property name="fields">jobOrderNo</property>
		</line>
		<line>
			<property name="head">工厂名称</property>
			<property name="width">5000</property>
			<property name="fields">factoryCode,factoryName</property>
		</line>
		<line>
			<property name="head">姓名</property>
			<property name="width">4000</property>
			<property name="fields">staffName</property>
		</line>
		<line>
			<property name="head">身份证号码</property>
			<property name="width">6000</property>
			<property name="fields">idCard</property>
		</line>
		<line>
			<property name="head">员工工号</property>
			<property name="width">6000</property>
			<property name="fields">staffCode</property>
		</line>
		<line>
			<property name="head">订单号</property>
			<property name="width">5000</property>
			<property name="fields">aufnr</property>
		</line>
		<line>
			<property name="head">包件编码</property>
			<property name="width">4000</property>
			<property name="fields">matnr</property>
		</line>
		<line>
			<property name="head">包件名称</property>
			<property name="width">8000</property>
			<property name="fields">maktx</property>
		</line>
		<line>
			<property name="head">板件编码</property>
			<property name="width">4000</property>
			<property name="fields">bmatnr</property>
		</line>
		<line>
			<property name="head">板件名称</property>
			<property name="width">4000</property>
			<property name="fields">bmaktx</property>
		</line>
		<line>
			<property name="head">子工序代码</property>
			<property name="width">4000</property>
			<property name="fields">stepCode</property>
		</line>
		<line>
			<property name="head">子工序名称</property>
			<property name="width">4000</property>
			<property name="fields">stepName</property>
		</line>
		<line>
			<property name="head">操作代码</property>
			<property name="width">4000</property>
			<property name="fields">oper</property>
		</line>
		<line>
			<property name="head">操作名称</property>
			<property name="width">4000</property>
			<property name="fields">operName</property>
		</line>
		<line>
			<property name="head">数量</property>
			<property name="width">4000</property>
			<property name="fields">num</property>
		</line>
		<line>
			<property name="head">单价</property>
			<property name="width">4000</property>
			<property name="fields">price</property>
		</line>
		<line>
			<property name="head">金额</property>
			<property name="width">4000</property>
			<property name="fields">amount</property>
		</line>
	</excel>
</export>

涉及jar包仅参考

poi-3.7-20101029.jar,poi-3.9-20121203.jar,poi-ooxml-3.9-20121203.jar,poi-ooxml-schemas-3.9-20121203.jar,poi-scratchpad-3.9-20121203.jar,ant.jar(压缩工具支持中文)

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java导出数据到Excel时,可能会因为数据过大导致前端请求超时。这是由于以下几个原因引起的: 1. 数据大:导出数据需要较长的处理时间和较高的系统资源消耗。如果数据远远超过前端请求能承受的极限,前端请求时间可能超出设置的时间限制,从而导致超时。 2. 网络传输限制:在数据较大的情况下,数据的传输时间也会相应增加。如果网络传输速度慢或网络连接不稳定,请求可能会在传输过程中超时。 为了解决这个问题,可以考虑以下几个方案: 1. 分批导出:将大数据分成小批次进行导出。可以在前端通过多次请求来获取分批次的数据,然后在后台进行数据导出。这样可以避免单次请求时间过长导致的超时问题。 2. 后台异步导出:当前端发起数据导出请求时,后台可以先返回一个任务标识,告知前端任务已经在后台进行处理。后台将数据导出的过程放在一个异步任务中执行,不会阻塞前端请求。前端可以轮询任务状态,直到任务完成并提供下载链接。 3. 压缩文件导出:将导出的数据以压缩文件的形式返回给前端,可以减小数据的传输,加快数据传输速度。前端可以在接收到文件后再进行解压和处理。 4. 前端分页获取:前端可以通过分页的方式获取数据,每次请求只获取一页的数据。这样可以减少单次请求的数据,降低请求超时的风险。 通过上述方案,可以有效地解决Java导出Excel数据大导致前端请求超时的问题,提高系统的可用性和用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值