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的行。

 1 import junit.framework.Assert;
 2 import org.apache.poi.ss.usermodel.Cell;
 3 import org.apache.poi.ss.usermodel.Row;
 4 import org.apache.poi.ss.usermodel.Sheet;
 5 import org.apache.poi.ss.usermodel.Workbook;
 6 import org.apache.poi.ss.util.CellReference;
 7 import org.apache.poi.xssf.streaming.SXSSFWorkbook;
 8 
 9     public static void main(String[] args) throws Throwable {
10 
11         // keep 100 rows in memory, exceeding rows will be flushed to disk
12         SXSSFWorkbook wb = new SXSSFWorkbook(100);
13         Sheet sh = wb.createSheet();
14         for(int rownum = 0; rownum < 1000; rownum++){
15             Row row = sh.createRow(rownum);
16             for(int cellnum = 0; cellnum < 10; cellnum++){
17                 Cell cell = row.createCell(cellnum);
18                 String address = new CellReference(cell).formatAsString();
19                 cell.setCellValue(address);
20             }
21         }
22 
23         // Rows with rownum < 900 are flushed and not accessible
24         for(int rownum = 0; rownum < 900; rownum++){
25           Assert.assertNull(sh.getRow(rownum));
26         }
27 
28         // ther last 100 rows are still in memory
29         for(int rownum = 900; rownum < 1000; rownum++){
30             Assert.assertNotNull(sh.getRow(rownum));
31         }
32         
33         FileOutputStream out = new FileOutputStream("/temp/sxssf.xlsx");
34         wb.write(out);
35         out.close();
36 
37         // dispose of temporary files backing this workbook on disk
38         wb.dispose();
39     }

三、封装后的工具类

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、依赖

 1 <dependency>
 2   <groupId>junit</groupId>
 3   <artifactId>junit</artifactId>
 4   <version>4.11</version>
 5   <scope>test</scope>
 6 </dependency>
 7 <dependency>
 8   <groupId>org.apache.poi</groupId>
 9   <artifactId>poi</artifactId>
10   <version>4.0.0</version>
11 </dependency>
12 <dependency>
13   <groupId>org.apache.poi</groupId>
14   <artifactId>poi-ooxml</artifactId>
15   <version>4.0.0</version>
16 </dependency>
17 <dependency>
18   <groupId>org.projectlombok</groupId>
19   <artifactId>lombok</artifactId>
20   <version>1.16.18</version>
21   <scope>compile</scope>
22 </dependency>

2、PoiExcelUtils工具类源码

  1 /**
  2  * Excel导出工具类
  3  */
  4 public class PoiExcelUtils {
  5 
  6   /**
  7    * 默认内存缓存数据量
  8    */
  9   public static final int BUFFER_SIZE = 100;
 10 
 11   /**
 12    * 默认每个sheet数据量
 13    */
 14   @SuppressWarnings("unused")
 15   public static final int DEFAULT_SHEET_SIZE = 50000;
 16 
 17   /**
 18    * 默认工作表名称
 19    */
 20   public static final String DEFAULT_SHEET_NAME = "sheet";
 21 
 22   /**
 23    * 导出数据到excel
 24    *
 25    * @param cols 列信息集合
 26    * @param dataGenerator 数据生成器
 27    * @param sheetName sheet名称前缀
 28    * @param outputStream 目标输出流
 29    */
 30   public static void export(List<ExcelColumn> cols, DataGenerator dataGenerator, String sheetName,
 31       OutputStream outputStream) {
 32 
 33     SXSSFWorkbook workbook = new SXSSFWorkbook(BUFFER_SIZE);
 34 
 35     try {
 36 
 37       // 从数据对象中获取列值使用的getter方法名集合
 38       List<String> methodNames = new ArrayList<>();
 39       String propertyName;
 40 
 41       for (ExcelColumn column : cols) {
 42         propertyName = "get" + upperCaseHead(column.getPropertyName());
 43         methodNames.add(propertyName);
 44       }
 45 
 46       List<?> objects;
 47 
 48       int i = 0;
 49 
 50       while (dataGenerator.hasNext()) {
 51 
 52         objects = dataGenerator.generate();
 53 
 54         SXSSFSheet sxssfSheet = createSheet(workbook, cols, sheetName + i);
 55         export2Sheet(sxssfSheet, methodNames, objects);
 56 
 57         objects.clear();
 58 
 59         System.out.println("Current batch >> " + (i + 1));
 60 
 61         i++;
 62       }
 63 
 64       // 输出
 65       workbook.write(outputStream);
 66 
 67     } catch (IOException e) {
 68       throw new RuntimeException(e);
 69     } finally {
 70       // dispose of temporary files backing this workbook on disk
 71       workbook.dispose();
 72     }
 73   }
 74 
 75   /**
 76    * 把数据导出到sheet中
 77    *
 78    * @param sheet sheet
 79    * @param getters 从数据对象中获取列值使用的getter方法名集合
 80    * @param data 数据
 81    */
 82   private static void export2Sheet(SXSSFSheet sheet, List<String> getters, List<?> data) {
 83 
 84     try {
 85 
 86       // 记录当前sheet的数据量
 87       int sheetRowCount = sheet.getLastRowNum();
 88 
 89       SXSSFRow dataRow;
 90 
 91       // 遍历数据集合
 92       for (Object datum : data) {
 93 
 94         // 创建一行
 95         dataRow = sheet.createRow(++sheetRowCount);
 96 
 97         Class<?> clazz = datum.getClass();
 98         Method readMethod;
 99         Object o;
100         XSSFRichTextString text;
101         Cell cell;
102 
103         // 遍历methodNames集合,获取每一列的值
104         for (int i = 0; i < getters.size(); i++) {
105           // 从Class对象获取getter方法
106           readMethod = clazz.getMethod(getters.get(i));
107           // 获取列值
108           o = readMethod.invoke(datum);
109           if (o == null) {
110             o = "";
111           }
112           text = new XSSFRichTextString(o.toString());
113           // 创建单元格并赋值
114           cell = dataRow.createCell(i);
115           cell.setCellValue(text);
116         }
117       }
118 
119     } catch (Exception e) {
120       throw new RuntimeException(e);
121     }
122   }
123 
124   /**
125    * 创建一个工作表
126    *
127    * @param workbook SXSSFWorkbook对象
128    * @param cols Excel导出列信息
129    * @param sheetName 工作表名称
130    * @return SXSSFSheet
131    */
132   private static SXSSFSheet createSheet(SXSSFWorkbook workbook, List<ExcelColumn> cols,
133       String sheetName) {
134 
135     // 创建一个sheet对象
136     SXSSFSheet sheet = workbook.createSheet(sheetName);
137 
138     // 生成表头
139     SXSSFRow row = sheet.createRow(0);
140 
141     ExcelColumn column;
142     SXSSFCell cell;
143     XSSFRichTextString text;
144 
145     for (int i = 0; i < cols.size(); i++) {
146 
147       // 获取列信息
148       column = cols.get(i);
149 
150       // 创建单元格
151       cell = row.createCell(i);
152 
153       // 为单元格赋值
154       text = new XSSFRichTextString(column.getName());
155       cell.setCellValue(text);
156 
157       // 设置列宽
158       int width = column.getWidth();
159 
160       if (width > 0) {
161         sheet.setColumnWidth(i, width);
162       }
163     }
164 
165     return sheet;
166   }
167 
168   /**
169    * 首字母转大写
170    *
171    * @param word 单词
172    * @return String
173    */
174   private static String upperCaseHead(String word) {
175     char[] chars = word.toCharArray();
176     int j = chars[0] - 32;
177     chars[0] = (char) j;
178     return new String(chars);
179   }
180 
181   /**
182    * 数据生成器
183    */
184   public interface DataGenerator {
185 
186     /**
187      * 是否还有数据
188      *
189      * @return boolean
190      */
191     boolean hasNext();
192 
193     /**
194      * 生成数据
195      *
196      * @return java.util.List
197      */
198     List<?> generate();
199   }
200 
201   /**
202    * 批次数据生成器
203    */
204   public static abstract class AbstractBatchDataGenerator implements DataGenerator {
205 
206     protected int batchNumber = 1;
207 
208     protected int totalBatch;
209 
210     protected int batchSize;
211 
212     public AbstractBatchDataGenerator(int batchSize) {
213       this.batchSize = batchSize;
214       this.totalBatch = getTotalBatch();
215     }
216 
217     /**
218      * 获取一共有多少批数据
219      *
220      * @return int
221      */
222     protected abstract int getTotalBatch();
223 
224     /**
225      * 获取下一批数据
226      *
227      * @param batchNumber 批次
228      * @param batchSize 批次数据量
229      * @return java.util.List
230      */
231     protected abstract List<?> nextBatch(int batchNumber, int batchSize);
232 
233     /**
234      * 是否有下一批数据
235      *
236      * @return boolean
237      */
238     @Override
239     public boolean hasNext() {
240       return this.batchNumber <= this.totalBatch;
241     }
242 
243     @Override
244     public List<?> generate() {
245 
246       if (hasNext()) {
247         List<?> batch = nextBatch(this.batchNumber, this.batchSize);
248         this.batchNumber++;
249         return batch;
250       }
251       return Collections.emptyList();
252     }
253   }
254 }
View Code

3、ExcelColumn类源码

 1 /**
 2  * 封装excel导出列信息
 3  */
 4 @Data
 5 @AllArgsConstructor
 6 @NoArgsConstructor
 7 public class ExcelColumn {
 8 
 9   /**
10    * 列名
11    */
12   private String name;
13 
14   /**
15    * 从数据对象中获取列值时使用的属性名
16    */
17   private String propertyName;
18 
19   /**
20    * 列宽度
21    */
22   private int width;
23 }

4、PoiExcelUtilsTest测试类

测试类导出1200万条数据,256MB内存。

运行java命令时添加-Xms256m -Xmx256m选项。

 1 /**
 2  * 测试excel操作工具类
 3  */
 4 public class PoiExcelUtilsTest {
 5 
 6   /**
 7    * 文件保存目录
 8    */
 9   private static final String UPLOAD_PATH = "D:/";
10 
11   /**
12    * 测试excel导出
13    */
14   @Test
15   public void testExport() {
16 
17     // 打印一下运行内存
18     long maxMemory = Runtime.getRuntime().maxMemory();
19     System.out.println(maxMemory / 1024 / 1024 + "MB");
20 
21     String filename = "TestPoi.xlsx";
22 
23     try (OutputStream outputStream = new FileOutputStream(UPLOAD_PATH + filename)) {
24 
25       int width = 10 * 512 + 500;
26 
27       List<ExcelColumn> cols = new ArrayList<>();
28       cols.add(new ExcelColumn("vin", "vin", width));
29       cols.add(new ExcelColumn("设备ID", "firmwareId", width));
30       cols.add(new ExcelColumn("升级状态", "updateStatus", width));
31       cols.add(new ExcelColumn("失败原因", "failReason", width));
32 
33       int size = 400000;
34 
35       PoiExcelUtils.export(
36           cols,
37           new TaskHistoryDataGenerator(size),
38           PoiExcelUtils.DEFAULT_SHEET_NAME,
39           outputStream);
40 
41     } catch (IOException e) {
42       throw new RuntimeException(e);
43     }
44   }
45 
46   /**
47    * TaskHistory数据生成器,测试使用
48    */
49   public static class TaskHistoryDataGenerator extends AbstractBatchDataGenerator {
50 
51     public TaskHistoryDataGenerator(int batchSize) {
52       super(batchSize);
53     }
54 
55     @Override
56     protected int getTotalBatch() {
57       return 30;
58     }
59 
60     @Override
61     protected List<?> nextBatch(int batchNumber, int batchSize) {
62 
63       List<TaskHistory> data = new ArrayList<>();
64 
65       int start = (batchNumber - 1) * batchSize;
66 
67       for (int i = 1; i <= batchSize; i++) {
68         int n = i + start;
69         TaskHistory taskHistory = new TaskHistory();
70         taskHistory.setFirmwareId(String.format("11%08d", n));
71         taskHistory.setFailReason("系统异常");
72         taskHistory.setUpdateStatus("请求成功");
73         taskHistory.setVin(String.format("1099728%08d", n));
74         data.add(taskHistory);
75       }
76 
77       return data;
78     }
79   }
80 
81   /**
82    * 封装测试数据
83    */
84   @Data
85   public static class TaskHistory {
86 
87     private String vin;
88 
89     private String updateStatus;
90 
91     private String firmwareId;
92 
93     private String failReason;
94   }
95 }
View Code
原文地址:https://www.cnblogs.com/xugf/p/11266723.html