POI SXSSF API 导出1000万数据示例
SXSSF是XSSF API的兼容流式扩展,在必须生成非常大的电子表格、并且堆空间有限时使用。
SXSSF通过限制对滑动窗口内数据的访问实现低内存占用,而XSSF允许访问文档中的所有行。
不在窗口中的数据将变得不可访问,因为它们已经被写入磁盘。
一、SXSSF流式API
首先看一下官方文档的说明。
https://poi.apache.org/components/spreadsheet/how-to.html#sxssf
SXSSF是XSSF API的兼容流式扩展,在必须生成非常大的电子表格、并且堆空间有限时使用。 SXSSF通过限制对滑动窗口内数据的访问实现低内存占用,而XSSF允许访问文档中的所有行。 不在窗口中的数据将变得不可访问,因为它们已经被写入磁盘。
可以通过SXSSFWorkbook(int windowSize)在工作簿创建时指定窗口大小,也可以通过SXSSFSheet.setRandomAccessWindowSize(int windowSize)在每个工作表中设置。
当通过createRow()创建新行并且未刷新记录的总数超过指定的窗口大小时,将刷新具有最低索引值的行数据,并且不能再通过getRow()访问该行。
默认窗口大小为100,由SXSSFWorkbook.DEFAULT_WINDOW_SIZE定义。
windowSize为-1表示无限制访问。在这种情况下,所有未通过调用flushRows()刷新的记录都可随机访问。
请注意,SXSSF通过调用dispose方法来分配必须始终明确清理的临时文件。
请注意,根据使用的功能不同,仍然可能会消耗大量内存,例如: 合并区域、超链接、注释等仍然只存储在内存中,因此如果广泛使用可能仍需要大量内存。
二、SXSSF示例
下面的示例写入一个包含100行窗口的工作表。
当行计数达到101时,rownum = 0的行被刷新到磁盘并从内存中删除,当rownum达到102时,则刷新rownum = 1的行。
import junit.framework.Assert;
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.usermodel.Workbook;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.streaming.SXSSFWorkbook; public static void main(String[] args) throws Throwable { // keep 100 rows in memory, exceeding rows will be flushed to disk
SXSSFWorkbook wb = new SXSSFWorkbook(100);
Sheet sh = wb.createSheet();
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);
}
} // Rows with rownum < 900 are flushed and not accessible
for(int rownum = 0; rownum < 900; rownum++){
Assert.assertNull(sh.getRow(rownum));
} // ther last 100 rows are still in memory
for(int rownum = 900; rownum < 1000; rownum++){
Assert.assertNotNull(sh.getRow(rownum));
} FileOutputStream out = new FileOutputStream("/temp/sxssf.xlsx");
wb.write(out);
out.close(); // dispose of temporary files backing this workbook on disk
wb.dispose();
}
三、封装后的工具类
1、PoiExcelUtils类
PoiExcelUtils类,封装了三个方法。
static void export(List<ExcelColumn> cols, DataGenerator dataGenerator, String sheetName, OutputStream outputStream)
该方法会创建一个SXSSFWorkbook对象,使用dataGenerator生成数据,每生成一批数据会生成一个sheet工作表,然后根据cols生成表头、获取数据写入到sheet工作表,当dataGenerator没有数据后,会输出到outputStream输出流,最后释放临时资源。
static void export2Sheet(SXSSFSheet sheet, List<String> getters, List<?> data)
这个是私有方法,不对外提供。作用是把一批数据写入到sheet工作表。
static SXSSFSheet createSheet(SXSSFWorkbook workbook, List<ExcelColumn> cols, String sheetName)
这个是私有方法,不对外提供。作用是在生成一批数据后生成一个新的sheet工作表。
2、ExcelColumn类
封装列信息,包括列名、从数据对象中获取列值时使用的属性名、列宽度等。
3、DataGenerator接口
用于生成数据。有两个方法:
boolean hasNext();
判断是否还有数据
List<?> generate();
生成一批数据
4、AbstractBatchDataGenerator抽象类
这是一个抽象批次数据生成器。
实现类DataGenerator接口,实现了hasNext和generate两个方法。
但是子类需要实现getTotalBatch和nextBatch两个方法,以便获取到批次数量和批次数据。
如果需要编写一个批次数据生成器,可以继承该抽象类。
5、TaskHistoryDataGenerator测试批次数据生成器
这是一个批次数据生成器,用于生成测试数据。
四、源代码
1、依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
<scope>compile</scope>
</dependency>
2、PoiExcelUtils工具类源码
/**
* Excel导出工具类
*/
public class PoiExcelUtils { /**
* 默认内存缓存数据量
*/
public static final int BUFFER_SIZE = 100; /**
* 默认每个sheet数据量
*/
@SuppressWarnings("unused")
public static final int DEFAULT_SHEET_SIZE = 50000; /**
* 默认工作表名称
*/
public static final String DEFAULT_SHEET_NAME = "sheet"; /**
* 导出数据到excel
*
* @param cols 列信息集合
* @param dataGenerator 数据生成器
* @param sheetName sheet名称前缀
* @param outputStream 目标输出流
*/
public static void export(List<ExcelColumn> cols, DataGenerator dataGenerator, String sheetName,
OutputStream outputStream) { SXSSFWorkbook workbook = new SXSSFWorkbook(BUFFER_SIZE); try { // 从数据对象中获取列值使用的getter方法名集合
List<String> methodNames = new ArrayList<>();
String propertyName; for (ExcelColumn column : cols) {
propertyName = "get" + upperCaseHead(column.getPropertyName());
methodNames.add(propertyName);
} List<?> objects; int i = 0; while (dataGenerator.hasNext()) { objects = dataGenerator.generate(); SXSSFSheet sxssfSheet = createSheet(workbook, cols, sheetName + i);
export2Sheet(sxssfSheet, methodNames, objects); objects.clear(); System.out.println("Current batch >> " + (i + 1)); i++;
} // 输出
workbook.write(outputStream); } catch (IOException e) {
throw new RuntimeException(e);
} finally {
// dispose of temporary files backing this workbook on disk
workbook.dispose();
}
} /**
* 把数据导出到sheet中
*
* @param sheet sheet
* @param getters 从数据对象中获取列值使用的getter方法名集合
* @param data 数据
*/
private static void export2Sheet(SXSSFSheet sheet, List<String> getters, List<?> data) { try { // 记录当前sheet的数据量
int sheetRowCount = sheet.getLastRowNum(); SXSSFRow dataRow; // 遍历数据集合
for (Object datum : data) { // 创建一行
dataRow = sheet.createRow(++sheetRowCount); Class<?> clazz = datum.getClass();
Method readMethod;
Object o;
XSSFRichTextString text;
Cell cell; // 遍历methodNames集合,获取每一列的值
for (int i = 0; i < getters.size(); i++) {
// 从Class对象获取getter方法
readMethod = clazz.getMethod(getters.get(i));
// 获取列值
o = readMethod.invoke(datum);
if (o == null) {
o = "";
}
text = new XSSFRichTextString(o.toString());
// 创建单元格并赋值
cell = dataRow.createCell(i);
cell.setCellValue(text);
}
} } catch (Exception e) {
throw new RuntimeException(e);
}
} /**
* 创建一个工作表
*
* @param workbook SXSSFWorkbook对象
* @param cols Excel导出列信息
* @param sheetName 工作表名称
* @return SXSSFSheet
*/
private static SXSSFSheet createSheet(SXSSFWorkbook workbook, List<ExcelColumn> cols,
String sheetName) { // 创建一个sheet对象
SXSSFSheet sheet = workbook.createSheet(sheetName); // 生成表头
SXSSFRow row = sheet.createRow(0); ExcelColumn column;
SXSSFCell cell;
XSSFRichTextString text; for (int i = 0; i < cols.size(); i++) { // 获取列信息
column = cols.get(i); // 创建单元格
cell = row.createCell(i); // 为单元格赋值
text = new XSSFRichTextString(column.getName());
cell.setCellValue(text); // 设置列宽
int width = column.getWidth(); if (width > 0) {
sheet.setColumnWidth(i, width);
}
} return sheet;
} /**
* 首字母转大写
*
* @param word 单词
* @return String
*/
private static String upperCaseHead(String word) {
char[] chars = word.toCharArray();
int j = chars[0] - 32;
chars[0] = (char) j;
return new String(chars);
} /**
* 数据生成器
*/
public interface DataGenerator { /**
* 是否还有数据
*
* @return boolean
*/
boolean hasNext(); /**
* 生成数据
*
* @return java.util.List
*/
List<?> generate();
} /**
* 批次数据生成器
*/
public static abstract class AbstractBatchDataGenerator implements DataGenerator { protected int batchNumber = 1; protected int totalBatch; protected int batchSize; public AbstractBatchDataGenerator(int batchSize) {
this.batchSize = batchSize;
this.totalBatch = getTotalBatch();
} /**
* 获取一共有多少批数据
*
* @return int
*/
protected abstract int getTotalBatch(); /**
* 获取下一批数据
*
* @param batchNumber 批次
* @param batchSize 批次数据量
* @return java.util.List
*/
protected abstract List<?> nextBatch(int batchNumber, int batchSize); /**
* 是否有下一批数据
*
* @return boolean
*/
@Override
public boolean hasNext() {
return this.batchNumber <= this.totalBatch;
} @Override
public List<?> generate() { if (hasNext()) {
List<?> batch = nextBatch(this.batchNumber, this.batchSize);
this.batchNumber++;
return batch;
}
return Collections.emptyList();
}
}
}
3、ExcelColumn类源码
/**
* 封装excel导出列信息
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ExcelColumn { /**
* 列名
*/
private String name; /**
* 从数据对象中获取列值时使用的属性名
*/
private String propertyName; /**
* 列宽度
*/
private int width;
}
4、PoiExcelUtilsTest测试类
测试类导出1200万条数据,256MB内存。
运行java命令时添加-Xms256m -Xmx256m选项。
/**
* 测试excel操作工具类
*/
public class PoiExcelUtilsTest { /**
* 文件保存目录
*/
private static final String UPLOAD_PATH = "D:/"; /**
* 测试excel导出
*/
@Test
public void testExport() { // 打印一下运行内存
long maxMemory = Runtime.getRuntime().maxMemory();
System.out.println(maxMemory / 1024 / 1024 + "MB"); String filename = "TestPoi.xlsx"; try (OutputStream outputStream = new FileOutputStream(UPLOAD_PATH + filename)) { int width = 10 * 512 + 500; List<ExcelColumn> cols = new ArrayList<>();
cols.add(new ExcelColumn("vin", "vin", width));
cols.add(new ExcelColumn("设备ID", "firmwareId", width));
cols.add(new ExcelColumn("升级状态", "updateStatus", width));
cols.add(new ExcelColumn("失败原因", "failReason", width)); int size = 400000; PoiExcelUtils.export(
cols,
new TaskHistoryDataGenerator(size),
PoiExcelUtils.DEFAULT_SHEET_NAME,
outputStream); } catch (IOException e) {
throw new RuntimeException(e);
}
} /**
* TaskHistory数据生成器,测试使用
*/
public static class TaskHistoryDataGenerator extends AbstractBatchDataGenerator { public TaskHistoryDataGenerator(int batchSize) {
super(batchSize);
} @Override
protected int getTotalBatch() {
return 30;
} @Override
protected List<?> nextBatch(int batchNumber, int batchSize) { List<TaskHistory> data = new ArrayList<>(); int start = (batchNumber - 1) * batchSize; for (int i = 1; i <= batchSize; i++) {
int n = i + start;
TaskHistory taskHistory = new TaskHistory();
taskHistory.setFirmwareId(String.format("11%08d", n));
taskHistory.setFailReason("系统异常");
taskHistory.setUpdateStatus("请求成功");
taskHistory.setVin(String.format("1099728%08d", n));
data.add(taskHistory);
} return data;
}
} /**
* 封装测试数据
*/
@Data
public static class TaskHistory { private String vin; private String updateStatus; private String firmwareId; private String failReason;
}
}
POI SXSSF API 导出1000万数据示例的更多相关文章
- JAVA使用POI如何导出百万级别数据(转)
https://blog.csdn.net/happyljw/article/details/52809244 用过POI的人都知道,在POI以前的版本中并不支持大数据量的处理,如果数据量过多还会 ...
- JAVA使用POI如何导出百万级别数据
用过POI的人都知道,在POI以前的版本中并不支持大数据量的处理,如果数据量过多还会常报OOM错误,这时候调整JVM的配置参数也不是一个好对策(注:jdk在32位系统中支持的内存不能超过2个G,而在6 ...
- JAVA使用POI如何导出百万级别数据(转载)
用过POI的人都知道,在POI以前的版本中并不支持大数据量的处理,如果数据量过多还会常报OOM错误,这时候调整JVM的配置参数也不是一个好对策(注:jdk在32位系统中支持的内存不能超过2个G,而在6 ...
- Oracle 快速插入1000万条数据的实现方式
1.使用dual配合connect by level create table BigTable as select rownum as id from dual connect by level & ...
- 使用POI导出百万级数据到excel的解决方案
1.HSSFWorkbook 和SXSSFWorkbook区别 HSSFWorkbook:是操作Excel2003以前(包括2003)的版本,扩展名是.xls,一张表最大支持65536行数据,256列 ...
- QTreeView处理大量数据(使用1000万条数据,每次都只是部分刷新)
如何使QTreeView快速显示1000万条数据,并且内存占用量少呢?这个问题困扰我很久,在网上找了好多相关资料,都没有找到合理的解决方案,今天在这里把我的解决方案提供给朋友们,供大家相互学习. 我开 ...
- 项目一:第十四天 1.在realm中动态授权 2.Shiro整合ehcache 缓存realm中授权信息 3.动态展示菜单数据 4.Quartz定时任务调度框架—Spring整合javamail发送邮件 5.基于poi实现分区导出
1 Shiro整合ehCache缓存授权信息 当需要进行权限校验时候:四种方式url拦截.注解.页面标签.代码级别,当需要验证权限会调用realm中的授权方法 Shiro框架内部整合好缓存管理器, ...
- JAVA Apache POI 之sax 解析10万级大数量数据
第一步让我们来看看我们的大量数据的excel 文件 好的下面第二步看一下代码: package com.chinait.utils; /** * 写这个东西主要是最近做了一个联通的数据迁移工作,他们就 ...
- java 使用POI导出百万级数据
先看结果吧,这只是测试其中有很多因数影响了性能. 表总数为:7千多万,测试导出100万 表字段有17个字段 最终excel大小有60多兆 总耗时:126165毫秒 差不多2分多钟 其核心简单来说就是分 ...
随机推荐
- [转]【会话技术】Cookie技术
建立时间:6.29 & 6.30 一.会话技术简介 1.存储客户端的状态 由一个问题引出今天的内容,例如网站的购物系统,用户将购买的商品信息存储到哪 里?因为Http协议是无状态的,也就是说 ...
- React源码 memo Fragment StrictMode cloneElement createFactory
1.memo react 16.6 推出的 api ,他的用意是给 function component 也有 PureComponent 这样一个类似的功能,因为我们知道 PureComponent ...
- Java 包扫描器
包扫描器 获取一个包下的所有类,然后使用默认的类加载器加载到内存中 public static List<Class<?>> scanByPackage(String pack ...
- GitHub 下载代码命令并且导入到IDEA环境
git clone项目到本地(项目有master和其他分支) 1.首先新建一个空文件夹,在文件夹里面git初始化操作,在文件夹的根目录下,右键选择git bash here,在弹出窗体中: ...
- 常用dos命令(2)
文件管理 type 显示文本文件的内容. copy 将一份或多份文件复制到另一个位置. del 删除一个或数个文件. move 移动文件并重命名文件和目录.(Windows XP Home Editi ...
- Linux中的关机操作
shutdown -h now //马上停止服务进行关机 shutdown -h 12:00 .//在12点后进行关机 shutdown -h +10 //在10分钟后进行关机 shutdown ...
- Spring Cloud微服务安全实战_1-1_导学
这两年微服务是一个很火的话题 .在java语言的体系里,现在最火的就是SpringCloud. 本系列文章主要不是讲:怎么使用SpringSpringCloud组件搭建一个微服务的体系,如服务的认证注 ...
- csv与openpyxl函数
csv 与openpyxl函数 csv函数 常用的存储数据的方式有两种--存储成csv格式文件.存储成Excel文件(不是复制黏贴的那种) 前面,我有讲到json是特殊的字符串.其实,csv也是一种字 ...
- python相对导包问题
导包分为:绝对路径.相对路径 在测试时发现不能够使用相对路径 查过之后才知道: 运行模块(如:main.py)中导包只能使用绝对路径,不能使用相对路径 官方文档: Note that relative ...
- [SDOI2018]物理实验 set,扫描线,旋转坐标系
[SDOI2018]物理实验 set,扫描线,旋转坐标系 链接 loj 思路 先将导轨移到原点,然后旋转坐标系,参考博客. 然后分线段,每段的贡献(三角函数值)求出来,用自己喜欢的平衡树,我选set. ...