JAVA 百万级数据导出

关注要点 

  1. 使用SXSSFWorkbook 专门处理大数据,对于大型excel的创建且不会内存溢出的。它的原理很简单,用硬盘空间换内存(就像hashmap用空间换时间一样)。 SXSSFWorkbook是streaming版本的XSSFWorkbook,它只会保存最新的excel rows在内存里供查看,在此之前的excel rows都会被写入到硬盘里(Windows电脑的话,是写入到C盘根目录下的temp文件夹)。
  2. 使用excel分页技术,假设如果有500w条数据,一下导入一个excel的sheet页中,想想打开excel也需要一段时间吧,慢的话有可能导致程序无法加载,或者直接结束进程的情况。
  3. 为了防止List装载数据过大造成内存溢出,采取分段获取数据方式,并在每次向row中加载完数据后对List进行手动回收,并将对象释放从而避免内存溢出。(如需实现方式请留言)。
/**
 * 百万级数据导出
 */
package XXX.XXX.XXX.XXX.impl;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;


/**
 * @author 紫气东来
 */
public class XxServiceImpl implements XxService {
	
	private XxDao dao;
	private SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
	private String fileName = df.format(Calendar.getInstance().getTime());
	private static String sheetName = "sheet_";
	private static final String SUFFIX = ".xlsx";
	//private String filePath = "D:" + File.separator + "XXX" + File.separator + "XXX" + File.separator + "XXX" + File.separator + fileName + SUFFIX;
	private String filePath = File.separator + "XXX" + File.separator + "XXX" + File.separator + "XXX" + File.separator + fileName + SUFFIX;
	public XxServiceImpl() {
	}

	public void setDao(XxDao dao) {
		this.dao = dao;
	}

	@SuppressWarnings("unchecked")
	@Override
	public File exportFile() {
		long startTime = System.currentTimeMillis();
		System.out.println("导出开始时间:"
				+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS")
						.format(startTime));
		File file = null;
		List ls = this.dao.queryResult();//获取数据源
		Integer dataCount = ls.size();
		if (0 == dataCount) return null;
		FileOutputStream out = null;
		SXSSFWorkbook workbook = null;
		SXSSFSheet sheet = null;
		int maxDataSize = 5000;
		int sheetCnt = 0;
		if (dataCount > maxDataSize) {
			if (0 == dataCount / maxDataSize) sheetCnt = dataCount / maxDataSize;
			else sheetCnt = dataCount / maxDataSize + 1;
		} else sheetCnt = 1;
		workbook = new SXSSFWorkbook(100);
		workbook.setCompressTempFiles(true);
		for (int i = 0; i < sheetCnt; i++) {
			sheet = (SXSSFSheet) workbook.createSheet(sheetName + (i + 1));
			sheet.setDefaultColumnWidth(25);
			SXSSFRow row = null;
			setHeader(row, sheet);
			Map<String, String> resultMap = new HashMap<String, String>();
			int curMaxDataSize = dataCount > (i + 1) * maxDataSize ? (i + 1)
					* maxDataSize : dataCount;
			int idx = 1;
			for (int j = i * maxDataSize; j < curMaxDataSize; j++) {
				resultMap = (Map<String, String>) ls.get(j);
				row = (SXSSFRow) sheet.createRow(idx ++);
				setRow(row, resultMap);
			}
			resultMap.clear();
			resultMap = null;
		}
		try {
			file = createFile(filePath);
			out = new FileOutputStream(file);
			workbook.write(out);
			out.close();
		} catch (FileNotFoundException e) {
			System.err.println(e.getMessage());
		} catch (IOException e) {
			System.err.println(e.getMessage());
		}
		long endTime = System.currentTimeMillis();
		System.out.println("导出结束时间:"
				+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS")
						.format(endTime));
		System.out.println("当前程序耗时:" + (endTime - startTime) + "ms" + " | 共:"
				+ dataCount + "条数据");
		return file;
	}
	
	private File createFile(String src) throws IOException {
		String path = src.substring(0, src.lastIndexOf(File.separator));
		String fileName = src.substring(src.lastIndexOf(File.separator) + 1, src.length());
		File f = new File(path);
		if (!f.exists()) f.mkdirs();
		File file = new File(f, fileName);
		if (!file.exists()) file.createNewFile();
		return file;
	}
	
	public void isToFile(InputStream is, File file) throws IOException {
		OutputStream os = null;
		try {
			os = new FileOutputStream(file);
			int len = 0;
			byte[] buffer = new byte[8192];
			while ((len = is.read(buffer)) != -1)
				os.write(buffer, 0, len);
		} finally {
			os.close();
			is.close();
		}
	}

	private void setHeader(SXSSFRow row, SXSSFSheet sheet) {
		row = (SXSSFRow) sheet.createRow(0);
		setRow(row, null);
	}
	
	private void setRow(SXSSFRow row, Map<String, String> resultMap) {
		if (resultMap == null) {
			getCell(row, 0).setCellValue("header1");
			getCell(row, 1).setCellValue("header2");
			getCell(row, 2).setCellValue("header3");
			getCell(row, 3).setCellValue("header4");
			getCell(row, 4).setCellValue("header5");
		} else {
			getCell(row, 0).setCellValue(resultMap.get("NAME1"));
			getCell(row, 1).setCellValue(resultMap.get("NAME2"));
			getCell(row, 2).setCellValue(resultMap.get("NAME3"));
			getCell(row, 3).setCellValue(resultMap.get("NAME4"));
			getCell(row, 4).setCellValue(resultMap.get("NAME5"));
		}
	}

	private Cell getCell(Row row, int index) {
		Cell cell = row.getCell(index);
		if (cell == null) cell = row.createCell(index);
		return cell;
	}

}

 

### 优化数据导出性能的策略 在处理百万级数据导出时,采用高效的库和优化策略至关重要。以下是几种推荐的方法: #### 使用 SXSSF (Streaming Usermodel API) Apache POI 提供了SXSSF(Streaming Usermodel API),专门用于处理大型Excel文件的创建。SXSSF通过将数据流式写入磁盘而不是全部加载到内存中,显著减少了内存占用,从而能够处理更大的数据集[^3]。 ```java import org.apache.poi.xssf.streaming.*; import org.apache.poi.ss.usermodel.*; public class LargeExcelExport { public static void main(String[] args) throws Exception { // 创建一个SXSSFWorkbook对象,设置缓存数为100 Workbook workbook = new SXSSFWorkbook(100); // keep 100 rows in memory, exceeding rows will be flushed to disk Sheet sheet = workbook.createSheet("Data"); // 模拟写入百万条记录 for (int i = 0; i < 1_000_000; i++) { Row row = sheet.createRow(i); Cell cell = row.createCell(0); cell.setCellValue("Row " + i); } // 写出到文件 try (FileOutputStream fos = new FileOutputStream("large_data.xlsx")) { workbook.write(fos); } // 关闭工作簿 workbook.close(); } } ``` #### 分页查询数据库 为了减少单次查询的数据量,可以使用分页查询技术从数据库获取数据。这样可以避免一次性加载过多数据到内存中,减少内存溢出的风险。 ```sql -- 示例SQL语句,用于分页查询 SELECT * FROM table_name LIMIT 1000 OFFSET 0; ``` #### 多线程处理 利用多线程并处理数据的读取和写入操作,可以进一步提高数据导出的速度。需要注意的是,多线程环境下要妥善管理资源,防止资源竞争导致的问题。 ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class MultiThreadExport { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(5); // 创建固定大小的线程池 // 提交任务给线程池 for (int i = 0; i < 5; i++) { final int threadId = i; executor.submit(() -> { // 在这里执数据导出任务 System.out.println("Executing task by thread " + threadId); }); } executor.shutdown(); // 关闭线程池 } } ``` #### 数据压缩 导出数据量巨大时,考虑对输出文件进压缩,可以有效减少存储空间的需求,并加快文件传输速度。 #### 避免不必要的对象创建 在循环内部频繁创建对象会导致垃圾回收器频繁运,影响程序性能。尽量复用对象或者提前分配好所需对象。 #### 调整JVM参数 适当调整Java虚拟机(JVM)的启动参数,如增加堆内存大小,可以帮助程序更好地处理大量数据。 #### 使用缓冲区 使用缓冲区来批量处理数据,减少I/O操作次数,提高效率。 #### 关闭不必要的功能 在导出过程中关闭不需要的功能,如自动计算公式等,以节省资源。 通过上述方法,可以有效地提升Java应用程序在导出百万级数据时的表现。每种方法都有其适用场景,实际应用时可能需要根据具体情况组合使用这些策略。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值