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万数据示例的更多相关文章

  1. JAVA使用POI如何导出百万级别数据(转)

    https://blog.csdn.net/happyljw/article/details/52809244   用过POI的人都知道,在POI以前的版本中并不支持大数据量的处理,如果数据量过多还会 ...

  2. JAVA使用POI如何导出百万级别数据

    用过POI的人都知道,在POI以前的版本中并不支持大数据量的处理,如果数据量过多还会常报OOM错误,这时候调整JVM的配置参数也不是一个好对策(注:jdk在32位系统中支持的内存不能超过2个G,而在6 ...

  3. JAVA使用POI如何导出百万级别数据(转载)

    用过POI的人都知道,在POI以前的版本中并不支持大数据量的处理,如果数据量过多还会常报OOM错误,这时候调整JVM的配置参数也不是一个好对策(注:jdk在32位系统中支持的内存不能超过2个G,而在6 ...

  4. Oracle 快速插入1000万条数据的实现方式

    1.使用dual配合connect by level create table BigTable as select rownum as id from dual connect by level & ...

  5. 使用POI导出百万级数据到excel的解决方案

    1.HSSFWorkbook 和SXSSFWorkbook区别 HSSFWorkbook:是操作Excel2003以前(包括2003)的版本,扩展名是.xls,一张表最大支持65536行数据,256列 ...

  6. QTreeView处理大量数据(使用1000万条数据,每次都只是部分刷新)

    如何使QTreeView快速显示1000万条数据,并且内存占用量少呢?这个问题困扰我很久,在网上找了好多相关资料,都没有找到合理的解决方案,今天在这里把我的解决方案提供给朋友们,供大家相互学习. 我开 ...

  7. 项目一:第十四天 1.在realm中动态授权 2.Shiro整合ehcache 缓存realm中授权信息 3.动态展示菜单数据 4.Quartz定时任务调度框架—Spring整合javamail发送邮件 5.基于poi实现分区导出

    1 Shiro整合ehCache缓存授权信息 当需要进行权限校验时候:四种方式url拦截.注解.页面标签.代码级别,当需要验证权限会调用realm中的授权方法   Shiro框架内部整合好缓存管理器, ...

  8. JAVA Apache POI 之sax 解析10万级大数量数据

    第一步让我们来看看我们的大量数据的excel 文件 好的下面第二步看一下代码: package com.chinait.utils; /** * 写这个东西主要是最近做了一个联通的数据迁移工作,他们就 ...

  9. java 使用POI导出百万级数据

    先看结果吧,这只是测试其中有很多因数影响了性能. 表总数为:7千多万,测试导出100万 表字段有17个字段 最终excel大小有60多兆 总耗时:126165毫秒 差不多2分多钟 其核心简单来说就是分 ...

随机推荐

  1. C语言形参跟实参详解

    形参与实参今天我们来说下c语言形参与实参的区别,形参跟实参理解的话也很简单,但是好多同学关于这个知识点都是一知半解,没有真正的去透彻,一问都知道,但在真正引用的时候还会出现很多问题,而百度的时候又会说 ...

  2. AXIOS 的请求

    AXIOS 本质上等同于json 传值 1.引用 //引入axios import Axios from 'axios' //将axios挂载到 Vue原型上 Vue.prototype.$https ...

  3. Nginx+Tomcat实现动静分离和负载均衡

    一.什么是动静分离? Nginx动静分离简单来说就是把动态和静态请求分开,不能理解成只是单纯的把动态页面和静态页面物理分离.严格意义上说应该是将动态请求和静态请求分开,可以理解成使用Nginx处理静态 ...

  4. WTL 9.0的变化 - atlctrls.h

    atlctrls.h中是对控件的封装. 第1249行增加: void GetMargins(UINT& nLeft, UINT& nRight) const { ATLASSERT(: ...

  5. opencart中文版checkout设置city和county为非必选

    opencart中文版在opencart原版进行了一些修改,添加了适合国内使用的设置,但是并不适合国外用户,比如订单页面的收货地址添加了国内的城市和区县,而国外的city和county被删减了,这两项 ...

  6. vue大文件上传组件选哪个好?

    需求:项目要支持大文件上传功能,经过讨论,初步将文件上传大小控制在500M内,因此自己需要在项目中进行文件上传部分的调整和配置,自己将大小都以501M来进行限制. 第一步: 前端修改 由于项目使用的是 ...

  7. 管理node.js的nvm

    我们坑同时在运行2个项目.而2个不同的项目所使用的node版本又不一样,或者是要用更新的node版本进行试验或学习.这种情况下,对于维护多个版本的node将会是一键非常麻烦的事情,而nvm就是为了解决 ...

  8. Windbg Assembly Code(反汇编)窗口的使用

    在WinDbg中,可以通过输入命令(u, ub, uu (Unassemble))或使用反汇编窗口查看程序汇编代码. 如何打开 DissAssembly Code窗口 通过菜单View-->Di ...

  9. linux定时器的实现方法

    Linux提供定时器机制,可以指定在未来的某个时刻发生某个事件,定时器的结构如下: struct timer_list { struct list_head list; unsigned long e ...

  10. 刷题记录and日记