`
zjcheng
  • 浏览: 91297 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

大批量数据导出Excel产生内存溢出解决方案

阅读更多
@SuppressWarnings("unchecked")
public class XlsMergeUtil {
	private static Logger logger = LoggerFactory.getLogger(XlsMergeUtil.class);

	/**
	 * 将多个Xls文件合并为一个,适用于只有一个sheet,并且格式相同的文档
	 * 
	 * @param inputs
	 *            输入的Xls文件,第一个XLS文件必须给出足够sheet空间 例如,总共200000行数据,第一个文件至少3个空白sheet
	 * @param out
	 *            输出文件
	 */
	public static void merge(InputStream[] inputs, OutputStream out) {
		if (inputs == null || inputs.length <= 1) {
			throw new IllegalArgumentException("没有传入输入流数组,或只有一个输入流.");
		}

		List<Record> rootRecords = getRecords(inputs[0]);
		Workbook workbook = Workbook.createWorkbook(rootRecords);
		List<Sheet> sheets = getSheets(workbook, rootRecords);
		if (sheets == null || sheets.size() == 0) {
			throw new IllegalArgumentException("第一篇文档的格式错误,必须有至少一个sheet");
		}
		// 以第一篇文档的第一个sheet为根,以后的数据都追加在这个sheet后面
		Sheet rootSheet = sheets.get(0);
		int rootRows = getRowsOfSheet(rootSheet); // 记录第一篇文档的行数,以后的行数在此基础上增加
		rootSheet.setLoc(rootSheet.getDimsLoc());
		Map<Integer, Integer> map = new HashMap(10000);
		int sheetIndex = 0;

		for (int i = 1; i < inputs.length; i++) { // 从第二篇开始遍历
			List<Record> records = getRecords(inputs[i]);
			// 达到最大行数限制,换一个sheet
			if (getRows(records) + rootRows >= RowRecord.MAX_ROW_NUMBER) {
				if ((++sheetIndex) > (sheets.size() - 1)) {
					logger.warn("第一个文档给出的sheets小于需要的数量,部分数据未能合并.");
					break;
				}
				rootSheet = sheets.get(sheetIndex);
				rootRows = getRowsOfSheet(rootSheet);
				rootSheet.setLoc(rootSheet.getDimsLoc());
				logger.debug("切换Sheet{}", sheetIndex);
			}
			int rowsOfCurXls = 0;
			// 遍历当前文档的每一个record
			for (Iterator itr = records.iterator(); itr.hasNext();) {
				Record record = (Record) itr.next();
				if (record.getSid() == RowRecord.sid) { // 如果是RowRecord
					RowRecord rowRecord = (RowRecord) record;
					// 调整行号
					rowRecord.setRowNumber(rootRows + rowRecord.getRowNumber());
					rootSheet.addRow(rowRecord); // 追加Row
					rowsOfCurXls++; // 记录当前文档的行数
				}
				// SST记录,SST保存xls文件中唯一的String,各个String都是对应着SST记录的索引
				else if (record.getSid() == SSTRecord.sid) {
					SSTRecord sstRecord = (SSTRecord) record;
					for (int j = 0; j < sstRecord.getNumUniqueStrings(); j++) {
						int index = workbook.addSSTString(sstRecord
								.getString(j));
						// 记录原来的索引和现在的索引的对应关系
						map.put(Integer.valueOf(j), Integer.valueOf(index));
					}
				} else if (record.getSid() == LabelSSTRecord.sid) {
					LabelSSTRecord label = (LabelSSTRecord) record;
					// 调整SST索引的对应关系
					label.setSSTIndex(map.get(Integer.valueOf(label
							.getSSTIndex())));
				}
				// 追加ValueCell
				if (record instanceof CellValueRecordInterface) {
					CellValueRecordInterface cell = (CellValueRecordInterface) record;
					int cellRow = cell.getRow() + rootRows;
					cell.setRow(cellRow);
					rootSheet.addValueRecord(cellRow, cell);
				}
			}
			rootRows += rowsOfCurXls;
		}

		byte[] data = getBytes(workbook, sheets.toArray(new Sheet[0]));
		write(out, data);
	}

	static void write(OutputStream out, byte[] data) {
		POIFSFileSystem fs = new POIFSFileSystem();
		// Write out the Workbook stream
		try {
			fs.createDocument(new ByteArrayInputStream(data), "Workbook");
			fs.writeFilesystem(out);
			out.flush();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				out.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	static List<Sheet> getSheets(Workbook workbook, List records) {
		int recOffset = workbook.getNumRecords();
		int sheetNum = 0;

		// convert all LabelRecord records to LabelSSTRecord
		convertLabelRecords(records, recOffset, workbook);
		List<Sheet> sheets = new ArrayList();
		while (recOffset < records.size()) {
			Sheet sh = Sheet.createSheet(records, sheetNum++, recOffset);

			recOffset = sh.getEofLoc() + 1;
			if (recOffset == 1) {
				break;
			}
			sheets.add(sh);
		}
		return sheets;
	}

	static int getRows(List<Record> records) {
		int row = 0;
		for (Iterator itr = records.iterator(); itr.hasNext();) {
			Record record = (Record) itr.next();
			if (record.getSid() == RowRecord.sid) {
				row++;
			}
		}
		return row;
	}

	static int getRowsOfSheet(Sheet sheet) {
		int rows = 0;
		sheet.setLoc(0);
		while (sheet.getNextRow() != null) {
			rows++;
		}
		return rows;
	}

	@SuppressWarnings("deprecation")
	static List<Record> getRecords(InputStream input) {
		try {
			POIFSFileSystem poifs = new POIFSFileSystem(input);
			InputStream stream = poifs.getRoot().createDocumentInputStream(
					"Workbook");
			return org.apache.poi.hssf.record.RecordFactory
					.createRecords(stream);
		} catch (IOException e) {
			logger.error("IO异常:{}", e.getMessage());
			e.printStackTrace();
		}
		return Collections.EMPTY_LIST;
	}

	static void convertLabelRecords(List records, int offset, Workbook workbook) {

		for (int k = offset; k < records.size(); k++) {
			Record rec = (Record) records.get(k);

			if (rec.getSid() == LabelRecord.sid) {
				LabelRecord oldrec = (LabelRecord) rec;

				records.remove(k);
				LabelSSTRecord newrec = new LabelSSTRecord();
				int stringid = workbook.addSSTString(new UnicodeString(oldrec
						.getValue()));

				newrec.setRow(oldrec.getRow());
				newrec.setColumn(oldrec.getColumn());
				newrec.setXFIndex(oldrec.getXFIndex());
				newrec.setSSTIndex(stringid);
				records.add(k, newrec);
			}
		}
	}

	public static byte[] getBytes(Workbook workbook, Sheet[] sheets) {
		// HSSFSheet[] sheets = getSheets();
		int nSheets = sheets.length;

		// before getting the workbook size we must tell the sheets that
		// serialization is about to occur.
		for (int i = 0; i < nSheets; i++) {
			sheets[i].preSerialize();
		}

		int totalsize = workbook.getSize();
		// pre-calculate all the sheet sizes and set BOF indexes
		int[] estimatedSheetSizes = new int[nSheets];
		for (int k = 0; k < nSheets; k++) {
			workbook.setSheetBof(k, totalsize);
			int sheetSize = sheets[k].getSize();
			estimatedSheetSizes[k] = sheetSize;
			totalsize += sheetSize;
		}
		logger.debug("分配内存{}bytes", totalsize);
		byte[] retval = new byte[totalsize];
		int pos = workbook.serialize(0, retval);

		for (int k = 0; k < nSheets; k++) {
			int serializedSize = sheets[k].serialize(pos, retval);
			if (serializedSize != estimatedSheetSizes[k]) {
				// Wrong offset values have been passed in the call to
				// setSheetBof()
				// above.
				// For books with more than one sheet, this discrepancy would
				// cause
				// excel
				// to report errors and loose data while reading the workbook
				throw new IllegalStateException(
						"Actual serialized sheet size (" + serializedSize
								+ ") differs from pre-calculated size ("
								+ estimatedSheetSizes[k] + ") for sheet (" + k
								+ ")");
				// TODO - add similar sanity check to ensure that
				// Sheet.serializeIndexRecord() does not
				// write mis-aligned offsets either
			}
			pos += serializedSize;
		}
		return retval;
	}

	public static void main(String[] args) throws Exception {
		final String PATH = "E:\\projects\\java\\ws_0\\export\\data\\";
		InputStream[] inputs = new InputStream[25];
		inputs[0] = new java.io.FileInputStream(PATH + "07_10.xls");
		for (int i = 1; i < 25; i++) {
			inputs[i] = new java.io.FileInputStream(PATH + "07_01.xls");
		}
		OutputStream out = new FileOutputStream(PATH + "xx.xls");
		long t1 = System.currentTimeMillis();
		merge(inputs, out);
		System.out.println(System.currentTimeMillis() - t1);
	}

}
分享到:
评论

相关推荐

    java解决大批量数据导出Excel产生内存溢出的方案

    在Java开发中,当面临大批量数据导出到Excel文件时,可能会遇到内存溢出的问题。这是因为Excel文件格式本身的设计,以及Java默认处理大数据的方式,可能导致内存占用过高,尤其是在一次性加载大量数据到内存中进行...

    大批量excel导出下载

    这个标题和描述提到的是一个针对批量导出Excel的工具类,它包括了两个核心类:`ExcelReader.java` 和 `ExcelWriter.java`,以及关于大批量导出的解决方案。下面将详细解释这些知识点。 1. **ExcelReader.java**: 这...

    java导出30万数据量的excel(采用生成多个excel,最后打包zip)

    在Java开发中,处理大数据量的Excel导出是一项常见的任务,尤其当数据量达到数十万条时,单个Excel文件可能会遇到性能瓶颈或格式限制。本项目针对这一问题提出了一种解决方案,即分块生成多个Excel文件,然后将它们...

    java多线程导出excel(千万级别)优化

    Java多线程导出Excel是处理大数据量时的一种高效策略,尤其在面对千万级别的数据时。传统的Apache POI库在处理大规模数据时可能会遇到栈溢出(StackOverflowError)和内存溢出(OutOfMemoryError)等问题,因为这些...

    easyExcel实现大数据导出

    `easyExcel`是专门为处理大量数据设计的一个轻量级工具,它能够有效地解决内存溢出问题,特别适合大数据量的Excel读写操作。下面将详细阐述`easyExcel`的核心功能、使用方法以及如何实现大数据导出。 `easyExcel`的...

    Excel大批量导入导出解决方案.docx

    ### Excel大批量导入导出解决方案 #### 概述 在处理大规模Excel文件的过程中,经常会遇到内存溢出或者频繁Full Garbage Collection (FGC)的问题,这些问题通常与Java中使用Apache POI库操作Excel文件的方式有关。...

    CSV大数据分批并压缩导出

    总的来说,CSV大数据分批并压缩导出是一种实用且高效的解决方案,特别适用于需要处理海量数据且内存资源有限的环境。它结合了分批处理的内存管理策略和压缩技术的空间优化,确保了大数据操作的可行性和性能。

    java实现csv导出千万级数据实例

    本实例聚焦于“java实现csv导出千万级数据实例”,旨在提供一个高效、稳定的解决方案,避免因数据量过大而导致的性能问题,如Java中的栈溢出(Stack Overflow)。CSV(Comma Separated Values)格式因其简单、通用性...

    Java使用POI导出大数据量Excel的方法

    在Java开发中,导出大数据量的Excel文件可能会面临内存溢出的问题,特别是在使用Apache POI库时。这是因为默认情况下,POI会将整个Excel工作簿存储在内存中,当数据量过大时,内存消耗非常显著,可能导致系统崩溃。...

    NPOI 2.0 DLL文件

    3. **大批量数据导出**:对于大数据量的应用场景,NPOI能有效地处理和导出大量数据,通过优化内存管理和流式处理,避免了内存溢出问题。这对于需要快速生成报表或分析结果的系统来说,是非常有用的。 4. **跨平台...

    alibaba easyexcel

    2. **性能卓越**:通过优化的数据解析和生成机制,EasyExcel在读写速度上表现出色,尤其适合处理大批量数据的导入导出操作。 3. **简单易用**:EasyExcel的API设计简洁,提供了丰富的注解和模板,使得开发者可以...

    alibaba-easyexcel.rar

    7. **批量操作**:对于大批量数据的导入导出,EasyExcel提供了批量处理功能,可以一次处理多个单元格,提高处理效率。 8. **兼容性**:EasyExcel支持多种Excel格式,包括老版本的 `.xls` 和新版本的 `.xlsx` 文件,...

Global site tag (gtag.js) - Google Analytics