源码分析:ApacePOI之SXSSF

源码分析:Apace POI:SXSSF

背景

说明

在一次程序部署过程中,由于疏忽(计划使用非root用户启动),使用了root启动程序,程序执行成功。

当使用非root角色启动程序时-报错Caused by: java.io.IOException: 权限不够

当发现这个错误时,怀疑是初次使用root角色启动程序,导致程序目录和文件权限被修改。

当第2次启动程序时,使用非root角色由于没有权限,导致无法正常执行。所以对整个程序目录进行重新赋权。但仍然报错。

只有使用root角色才可以执行成功。

最后,通过查找资料,发现原因如下:

由于程序使用了ApacePOI的SXSSF,由于未指定临时文件目录,使用了默认的目录/tmp。

第1次使用root启动程序时,在/tmp目录下创建了临时poi目录poifiles,导致poifiles目录只允许root操作;

第2次使用非root启动程序时,由于也需要用到临时poi目录poifiles,因为没有权限,导致程序报错。

报错重现

主要程序代码

public static void main(String[] args) throws IOException {
    System.out.println("start");
    String dataPath = "1.xlsx";
    System.out.println("data path : " + dataPath);
    SXSSFWorkbook workbook = new SXSSFWorkbook(-1);
    String[] title = {"1","2","3"};
    for (String str : title) {
        Sheet sheet = workbook.createSheet(str);
    }
    FileOutputStream fileOutputStream = new FileOutputStream(dataPath);
    workbook.write(fileOutputStream);
    workbook.close();
    System.out.println("finally");
}

使用root启动程序

#  /soft/jdk1.8.0_181/bin/java -jar POIProject.jar
start
data path : 1.xlsx
finally

使用非root启动程序

#  /soft/jdk1.8.0_181/bin/java -jar POIProject.jar
start
data path : 1.xlsx
Exception in thread "main" java.lang.RuntimeException: java.io.IOException: 权限不够
	at org.apache.poi.xssf.streaming.SXSSFWorkbook.createAndRegisterSXSSFSheet(SXSSFWorkbook.java:688)
	at org.apache.poi.xssf.streaming.SXSSFWorkbook.createSheet(SXSSFWorkbook.java:705)
	at com.sequoiadb.Test.main(Test.java:21)
Caused by: java.io.IOException: 权限不够
	at java.io.UnixFileSystem.createFileExclusively(Native Method)
	at java.io.File.createTempFile(File.java:2024)
	at org.apache.poi.util.DefaultTempFileCreationStrategy.createTempFile(DefaultTempFileCreationStrategy.java:110)
	at org.apache.poi.util.TempFile.createTempFile(TempFile.java:66)
	at org.apache.poi.xssf.streaming.SheetDataWriter.createTempFile(SheetDataWriter.java:89)
	at org.apache.poi.xssf.streaming.SheetDataWriter.<init>(SheetDataWriter.java:72)
	at org.apache.poi.xssf.streaming.SheetDataWriter.<init>(SheetDataWriter.java:77)
	at org.apache.poi.xssf.streaming.SXSSFWorkbook.createSheetDataWriter(SXSSFWorkbook.java:342)
	at org.apache.poi.xssf.streaming.SXSSFSheet.<init>(SXSSFSheet.java:80)
	at org.apache.poi.xssf.streaming.SXSSFWorkbook.createAndRegisterSXSSFSheet(SXSSFWorkbook.java:684)
	... 2 more

查看临时目录/tmp下的文件夹poifiles情况

$ ll /tmp/
总用量 1288
drwxr-xr-x. 2 root     root                6 515 11:31 poifiles

文件夹poifiles的拥有者是root

解决方案

2种方式都可以作为解决方案

  1. 将目录poifiles删除,重启程序使用非root用户创建
  2. 修改目录poifiles的拥有者

总结

  1. 由于报错信息比较明显,所以文件权限的方向是对的,但由于没有想到程序开发者对ApacePOI的SXSSF的临时文件目录处理问题,导致这一问题排查了一段时间

  2. 另外,在开发时,应该指定临时poi目录,放到程序目录下,而不是使用默认的文件目录

所以,为了解决这一问题,梳理了ApacePOI的SXSSF相关源码。内容如下:

参考链接

Apace POI官网

Apache POI - the Java API for Microsoft Documents

SXSSF相关

The New Halloween Document (apache.org)

源码分析

Apache-poi SXSSFWorkbook 流处理_小码氓的博客-CSDN博客_poi sxssfworkbook

Excel工具-SXSSFWorkbook 研究与低内存占用分析 - 掘金 (juejin.cn)

说明

Apace POI是什么

在官网中就有相关说明,如下:

The Apache POI project is the master project for developing pure Java ports of file formats based on Microsoft’s OLE 2 Compound Document Format. OLE 2 Compound Document Format is used by Microsoft Office Documents, as well as by programs using MFC property sets to serialize their document objects.

Apache POI是基于微软的OLE2复合文档格式的Java开发接口和方法的主项目。

简单地可以理解Apache POI是使用Java操作微软文档的框架。

Apace POI常用类

在Apace POI中,有对不同文档对象的不同操作类,如下:

Excel (HSSF/XSSF)
PowerPoint (HSLF/XSLF)
Word (HWPF/XWPF)
Outlook (HSMF)
Visio (HDGF+XDGF)
Publisher (HPBF)
OLE2 Filesystem (POIFS)
OLE2 Document Props (HPSF)
TNEF (HMEF) for winmail.dat
OpenXML4J (OOXML)

当需要时,可以使用相应方法实现。

SXSSF是什么

SXSSF的面向的文档对象是Excel。与同为操作Excel的HSSF和XSSF区别是:

SXSSF(包:org.apache.poi.xssf.streaming)是 XSSF 的 API 兼容的流式扩展。

如官网描述:

SXSSF (package: org.apache.poi.xssf.streaming) is an API-compatible streaming extension of XSSF to be used when very large spreadsheets have to be produced, and heap space is limited.

翻译:SXSSF是 XSSF 的 API 兼容的流式扩展,用于必须生成非常大的电子表格并且内存空间有限时使用。

SXSSF是XSSF处理大数据的解决方案:在处理大量数据时,XSSF常常会因为占用内存过多而报错OOM,如下:

XSSFWorkbook读取excel模版写入数据过多造成OOM,使用SXSSFWorkbook解决_FinelyYang的博客-CSDN博客_sxssfworkbook读取excel

SXSSF实现方式

如官网描述

SXSSF achieves its low memory footprint by limiting access to the rows that are within a sliding window, while XSSF gives access to all rows in the document.

翻译:SXSSF 通过限制对滑动窗口内行的访问来实现其低内存占用,而 XSSF 允许访问文档中的所有行。

可以理解为:和HashMap用空间换时间类似,SXSSF是硬盘空间换内存。当数据量超过设置值是,会将保存在内存的数据持久化到硬盘中,即临时文件夹目录下中。

这样处理,存在局限性,如下:

Older rows that are no longer in the window become inaccessible, as they are written to the disk.

翻译:不再在窗口中的旧行变得不可访问,因为它们被写入磁盘。

关系说明

Apace POI和Excel存在以下关系:

  1. 一个Excel就是一个工作簿(Workbook)
  2. 一个Sheet就是一张工作表
  3. 一个Workbook可以包含多个Sheet
  4. 每一行Row的每一列就是一个单元格(Cell)

源码分析

参考

SXSSFWorkbook (POI API Documentation) (apache.org)

SXSSFWorkbook上下关系

SXSSFWorkbook是接口Workbook的实现类,

public class SXSSFWorkbook implements Workbook {

}

对于接口Workbook有以下实现类:

public final class HSSFWorkbook extends POIDocument implements Workbook {}
public class SXSSFWorkbook implements Workbook {}
public class XSSFWorkbook extends POIXMLDocument implements Workbook, Date1904Support {}

其中HSSFWorkbook是操作Microsoft Excel XLS 格式,XSSFWorkbook是操作Microsoft Excel OOXML XLSX 格式。而SXSSFWorkbook是XSSFWorkbook流式扩展。

SXSSFWorkbook存在6个构造函数,如下:

SXSSFWorkbook的构造器

默认无参构造器

public SXSSFWorkbook() {
    this((XSSFWorkbook)null);
}

这也说明:SXSSFWorkbook是XSSFWorkbook流式扩展

参数workbook构造器

public SXSSFWorkbook(XSSFWorkbook workbook) {
    this(workbook, 100);
}

指定窗口数量构造器

public SXSSFWorkbook(XSSFWorkbook workbook, int rowAccessWindowSize) {
    this(workbook, rowAccessWindowSize, false);
}

rowAccessWindowSize:刷新前保留在内存中的行数

  1. 默认窗口大小为 100,由 SXSSFWorkbook.DEFAULT_WINDOW_SIZE 定义

指定是否压缩临时文件

public SXSSFWorkbook(XSSFWorkbook workbook, int rowAccessWindowSize, boolean compressTmpFiles) {
    this(workbook, rowAccessWindowSize, compressTmpFiles, false);
}

compressTmpFiles:是否对临时文件使用gzip压缩

最终构造器

无论是哪一种实例化,都会进入构造函数public SXSSFWorkbook(XSSFWorkbook workbook, int rowAccessWindowSize, boolean compressTmpFiles, boolean useSharedStringsTable)

public SXSSFWorkbook(XSSFWorkbook workbook, int rowAccessWindowSize, boolean compressTmpFiles, boolean useSharedStringsTable) {}

方法内主要是对以上的参数进行赋值,

其中对于参数int rowAccessWindowSize,如下:

private void setRandomAccessWindowSize(int rowAccessWindowSize) {
    if (rowAccessWindowSize != 0 && rowAccessWindowSize >= -1) {
        this._randomAccessWindowSize = rowAccessWindowSize;
    } else {
        throw new IllegalArgumentException("rowAccessWindowSize must be greater than 0 or -1");
    }
}

当rowAccessWindowSize小于-1或者rowAccessWindowSize等于0时,将报错:rowAccessWindowSize must be greater than 0 or -1;这是因为:

  1. 值-1表示无限制访问。在这种情况下,所有未通过调用flush()刷新的记录都可以随机访问
  2. 不允许值0,因为它将刷新任何新创建的行,而没有机会指定任何单元格

对于参数XSSFWorkbook workbook,如下:

if (workbook == null) {
    this._wb = new XSSFWorkbook();
    this._sharedStringSource = useSharedStringsTable ? this._wb.getSharedStringSource() : null;
} else {
    this._wb = workbook;
    this._sharedStringSource = useSharedStringsTable ? this._wb.getSharedStringSource() : null;
    Iterator var5 = this._wb.iterator();

    while(var5.hasNext()) {
        Sheet sheet = (Sheet)var5.next();
        this.createAndRegisterSXSSFSheet((XSSFSheet)sheet);
    }
}

其中参数boolean useSharedStringsTable表示是否使用共享字符串表,默认为false。

根据实际情况来设置,若导出的数据中有大量重复的字符串,则可以设置开启。

参数XSSFWorkbook workbook若没有则创建一个新的XSSFWorkbook对象。

完成SXSSFWorkbook的实例化后,可以操作(创建)Sheet

SXSSFWorkbook创建Sheet

逻辑入口

public SXSSFSheet createSheet(String sheetname) {
    return this.createAndRegisterSXSSFSheet(this._wb.createSheet(sheetname));
}

对于SXSSFSheet的定义,和SXSSFWorkbook(XSSFWorkbook workbook)一样,SXSSFSheet(this, xSheet)

SXSSFSheet createAndRegisterSXSSFSheet(XSSFSheet xSheet) {
    SXSSFSheet sxSheet;
    try {
        sxSheet = new SXSSFSheet(this, xSheet);
    } catch (IOException var4) {
        throw new RuntimeException(var4);
    }

    this.registerSheetMapping(sxSheet, xSheet);
    return sxSheet;
}

其中SXSSFSheet的实例化

public SXSSFSheet(SXSSFWorkbook workbook, XSSFSheet xSheet) throws IOException {
    this._workbook = workbook;
    this._sh = xSheet;
    this._writer = workbook.createSheetDataWriter();
    this.setRandomAccessWindowSize(this._workbook.getRandomAccessWindowSize());
    this._autoSizeColumnTracker = new AutoSizeColumnTracker(this);
}

对于workbook.createSheetDataWriter()

protected SheetDataWriter createSheetDataWriter() throws IOException {
    return (SheetDataWriter)(this._compressTmpFiles ? new GZIPSheetDataWriter(this._sharedStringSource) : new SheetDataWriter(this._sharedStringSource));
}

对于this._compressTmpFiles不同取值,进入不同的创建模式:

  1. 当this._compressTmpFiles==true,进入GZIPSheetDataWriter(this._sharedStringSource)
  2. 当this._compressTmpFiles==false,进入new SheetDataWriter(this._sharedStringSource)

对象GZIPSheetDataWriter继承自SheetDataWriter

public class GZIPSheetDataWriter extends SheetDataWriter {
    public GZIPSheetDataWriter(SharedStringsTable sharedStringsTable) throws IOException {
        super(sharedStringsTable);
    }
}

即总会进入构造器,如下:

public SheetDataWriter(SharedStringsTable sharedStringsTable) throws IOException {
    this();
    this._sharedStringSource = sharedStringsTable;
}

而SheetDataWriter无参构造器如下:

public SheetDataWriter() throws IOException {
    this._numberLastFlushedRow = -1;
    this._fd = this.createTempFile();
    this._out = this.createWriter(this._fd);
}

其中this._fd = this.createTempFile()就是创建临时文件

public File createTempFile() throws IOException {
    return TempFile.createTempFile("poi-sxssf-sheet", ".xml");
}

而创建临时文件主要通过TempFile.createTempFile()实现

临时文件工具类TempFile

public final class TempFile {
    // 实例化临时文件策略
    private static TempFileCreationStrategy strategy = new DefaultTempFileCreationStrategy();
    // 
    public static final String JAVA_IO_TMPDIR = "java.io.tmpdir";
	// 无参构造器
    private TempFile() {
    }
	// 重设临时文件策略
    public static void setTempFileCreationStrategy(TempFileCreationStrategy strategy) {
        if (strategy == null) {
            throw new IllegalArgumentException("strategy == null");
        } else {
            TempFile.strategy = strategy;
        }
    }
	// 创建临时文件
    public static File createTempFile(String prefix, String suffix) throws IOException {
        return strategy.createTempFile(prefix, suffix);
    }
	// 创建临时文件夹
    public static File createTempDirectory(String name) throws IOException {
        return strategy.createTempDirectory(name);
    }
}

由于构造方法TempFile()为private,而两个重要方法createTempFilecreateTempDirectory为静态方法,所以外部可以直接调用。

当不实现重设临时文件创建策略setTempFileCreationStrategy时,将使用默认临时文件创建策略 new DefaultTempFileCreationStrategy进行创建

临时文件创建策略类

public class DefaultTempFileCreationStrategy implements TempFileCreationStrategy {

}

默认临时文件创建策略类DefaultTempFileCreationStrategy实现了接口TempFileCreationStrategy

public interface TempFileCreationStrategy {
    File createTempFile(String var1, String var2) throws IOException;
    File createTempDirectory(String var1) throws IOException;
}

DefaultTempFileCreationStrategy实现的两个方法均进入方法createPOIFilesDirectory()

public File createTempFile(String prefix, String suffix) throws IOException {
	this.createPOIFilesDirectory();
}
public File createTempDirectory(String prefix) throws IOException {
	this.createPOIFilesDirectory();
}

对于方法createPOIFilesDirectory

private void createPOIFilesDirectory() throws IOException {
    if (this.dir == null) {
        String tmpDir = System.getProperty("java.io.tmpdir");
        if (tmpDir == null) {
            throw new IOException("Systems temporary directory not defined - set the -Djava.io.tmpdir jvm property!");
        }

        this.dir = new File(tmpDir, "poifiles");
    }

    this.createTempDirectory(this.dir);
}

由于在TempFile使用的无参构造器,如下:

public DefaultTempFileCreationStrategy() {
    this((File)null);
}
public DefaultTempFileCreationStrategy(File dir) {
    this.dir = dir;
}

所以,将使用以下逻辑创建临时目录

if (this.dir == null) {
    String tmpDir = System.getProperty("java.io.tmpdir");
    if (tmpDir == null) {
        throw new IOException("Systems temporary directory not defined - set the -Djava.io.tmpdir jvm property!");
    }
    this.dir = new File(tmpDir, "poifiles");
}

对于window环境,java.io.tmpdir的位置为:C:\Users\Administrator\AppData\Local\Temp

对于linux环境,java.io.tmpdir的位置为:/tmp

使用默认临时文件夹路径

当不指定DefaultTempFileCreationStrategy(File dir)中的File dir将使用默认路径创建临时文件夹和临时文件,如上

使用指定临时文件夹路径

使用默认的/tmp作为临时文件路径,一方面不方便文件的管理,如遇到背景中的文件权限问题;另外一方面,如果在程序执行过程中,对临时文件进行操作,如删除等等,将影响程序正常运行等等问题。应该将这一临时文件路径指定到程序目录下。实现方式如下:

// SXSSFWorkbook workbook = new SXSSFWorkbook(100);
String excelSXSSFWorkbookTmpPath = "poifiles";
File dir = new File(excelSXSSFWorkbookTmpPath);
if (!dir.exists()) {
    dir.mkdirs();
}
TempFile.setTempFileCreationStrategy(new DefaultTempFileCreationStrategy(dir));
// workbook.createSheet("");

通过对TempFile创建临时文件策略的变更,使跳过if (this.dir == null) {},执行使用指定的路径this.createTempDirectory(this.dir)创建临时文件poifiles。如下:

private void createPOIFilesDirectory() throws IOException {
    // String excelSXSSFWorkbookTmpPath = "poifiles";
    // File dir = new File(excelSXSSFWorkbookTmpPath);
    if (this.dir == null) {
        String tmpDir = System.getProperty("java.io.tmpdir");
        if (tmpDir == null) {
            throw new IOException("Systems temporary directory not defined - set the -Djava.io.tmpdir jvm property!");
        }
        this.dir = new File(tmpDir, "poifiles");
    }
    this.createTempDirectory(this.dir);
}

创建临时文件

当完成POI临时文件夹的创建后,会在临时文件夹下创建临时文件,如下:

public File createTempFile(String prefix, String suffix) throws IOException {
    this.createPOIFilesDirectory();
    // 创建临时文件
    // this.dir:临时文件目录
    // prefix:文件前缀
    // suffix: 文件后缀
    // 在目录下会生成文件: poi-sxssf-sheet683480813396732906.xml
    File newFile = File.createTempFile(prefix, suffix, this.dir);
    // 由于未设置系统属性poi.keep.tmp.files,所以创建的临时文件将在程序退出时被删除
    if (System.getProperty("poi.keep.tmp.files") == null) {
        newFile.deleteOnExit();
    }
    return newFile;
}

所以创建工作表sheet产生的临时文件的删除是由标准类File的方法deleteOnExit()实现的。

创建row

完成SXSSFWorkbook、Sheet创建后,可以使用Sheet的createRow()写入数据行

Row row = sh.createRow(rownum);

在方法Sheet.createRow(int rownum)中,有以下内容

int maxrow = SpreadsheetVersion.EXCEL2007.getLastRowIndex();

maxrow为EXCEL2007的最大行数,为1048575

当超过这一行数时,将抛出异常:

throw new IllegalArgumentException("Invalid row number (" + rownum + ") outside allowable range (0.." + maxrow + ")");

验证表格的合理性

// 当前的记录数小于最后刷盘记录数
if (rownum <= this._writer.getLastFlushedRow()) {
                throw new IllegalArgumentException("Attempting to write a row[" + rownum + "] in the range [0," + this._writer.getLastFlushedRow() + "] that is already written to disk.");
} 
else if (this._sh.getPhysicalNumberOfRows() > 0 && rownum <= this._sh.getLastRowNum()) {
                throw new IllegalArgumentException("Attempting to write a row[" + rownum + "] in the range [0," + this._sh.getLastRowNum() + "] that is already written to disk.");
 }

从内存写入文件

SXSSFRow newRow = new SXSSFRow(this);
this._rows.put(rownum, newRow);
this.allFlushed = false;

if (this._randomAccessWindowSize >= 0 && this._rows.size() > this._randomAccessWindowSize) {
    try {
        this.flushRows(this._randomAccessWindowSize);
    } catch (IOException var5) {
        throw new RuntimeException(var5);
    }
}
return newRow;

如果当前行数大于窗口大小,则进入方法this.flushRows(this._randomAccessWindowSize)

public void flushRows(int remaining) throws IOException {
    while(this._rows.size() > remaining) {
        this.flushOneRow();
    }
    if (remaining == 0) {
        this.allFlushed = true;
    }
}
private void flushOneRow() throws IOException {
    Integer firstRowNum = (Integer)this._rows.firstKey();
    if (firstRowNum != null) {
        int rowIndex = firstRowNum;
        SXSSFRow row = (SXSSFRow)this._rows.get(firstRowNum);
        this._autoSizeColumnTracker.updateColumnWidths(row);
        this._writer.writeRow(rowIndex, row);
        this._rows.remove(firstRowNum);
        this.lastFlushedRowNumber = rowIndex;
    }

}

写盘关键代码逻辑如下:

// SXSSFSheet
private void flushOneRow() throws IOException {
    this._writer.writeRow(rowIndex, row);
}
// SheetDataWriter
public void writeRow(int rownum, SXSSFRow row) throws IOException {
    while(cells.hasNext()) {
		this.writeCell(var4++, (Cell)cells.next());
    }
}
// SheetDataWriter
 public void writeCell(int columnIndex, Cell cell) throws IOException {
     //...主要逻辑
 }

写入数据到Excel

FileOutputStream out = new FileOutputStream("sxssf.xlsx");
wb.write(out);

通过方法SXSSFWorkbook.write(FileOutputStream out)持久化到文件中

public void write(OutputStream stream) throws IOException {}

方法中主要逻辑如下:

写入临时文件中

把最后不足randomAccessWindowSize 的行数写入sheet临时文件。

this.flushSheets();
File tmplFile = TempFile.createTempFile("poi-sxssf-template", ".xlsx");

FileOutputStream os = new FileOutputStream(tmplFile);
Throwable var5 = null;

try {
    this._wb.write(os);
} catch (Throwable var67) {
    var5 = var67;
    throw var67;
} finally {
    if (os != null) {
        if (var5 != null) {
            try {
                os.close();
            } catch (Throwable var63) {
                var5.addSuppressed(var63);
            }
        } else {
            os.close();
        }
    }

}

其中this._wb.write(os)是将workbook中所有的数据都写入刚刚创建的tmplFile临时文件中

临时文件写入目标Excel

ZipSecureFile zf = new ZipSecureFile(tmplFile);
var5 = null;
try {
    ZipFileZipEntrySource source = new ZipFileZipEntrySource(zf);
    Throwable var7 = null;

    try {
        this.injectData(source, stream);
    } catch (Throwable var66) {
        var7 = var66;
        throw var66;
    } finally {
        if (source != null) {
            if (var7 != null) {
                try {
                    source.close();
                } catch (Throwable var65) {
                    var7.addSuppressed(var65);
                }
            } else {
                source.close();
            }
        }

    }
} catch (Throwable var70) {
    var5 = var70;
    throw var70;
} finally {
    if (zf != null) {
        if (var5 != null) {
            try {
                zf.close();
            } catch (Throwable var64) {
                var5.addSuppressed(var64);
            }
        } else {
            zf.close();
        }
    }
}

删除临时文件

boolean deleted;
try {
    //...
}
finally {
	deleted = tmplFile.delete();
}
if (!deleted) {
	throw new IOException("Could not delete temporary file after processing: " + tmplFile);
}

这里的临时文件指的是SXSSFWorkbook临时文件,即poi-sxssf-templat-xxx.xlsx。

而sheet的临时文件是:poi-sxssf-sheet-xxx.xml

sheet的临时文件是通过标准类File的方法deleteOnExit()实现的

SXSSFWorkbook处理

在官方文档的代码示例中,在处理完逻辑后,有以下代码:

// dispose of temporary files backing this workbook on disk
// 处理磁盘上支持此工作簿的临时文件
wb.dispose()
// SXSSFWorkbook
public boolean dispose() {
    boolean success = true;
    Iterator var2 = this._sxFromXHash.keySet().iterator();

    while(var2.hasNext()) {
        SXSSFSheet sheet = (SXSSFSheet)var2.next();
        try {
            success = sheet.dispose() && success;
        } catch (IOException var5) {
            logger.log(5, new Object[]{var5});
            success = false;
        }
    }
    return success;
}
// SXSSFSheet
boolean dispose() throws IOException {
    if (!this.allFlushed) {
        this.flushRows();
    }

    return this._writer.dispose();
}
//SheetDataWriter
boolean dispose() throws IOException {
    boolean ret;
    try {
        this._out.close();
    } finally {
        ret = this._fd.delete();
    }
    return ret;
}

可以看到最终的效果也是对文件的删除

总结

整个逻辑非常简单,如下:

  1. 将内存中剩余不足randomAccessSize数目的数据,先写入sheet临时文件poi-sxssf-sheet-xxx.xml中
  2. 将Workbook中所有数据(多个sheet临时文件)写入临时文件poi-sxssf-templat-xxx.xlsx中
  3. 将poi-sxssf-templat-xxx.xlsx写入目标文件中
  4. 删除文件poi-sxssf-templat-xxx.xlsx,对于poi-sxssf-sheet-xxx.xml是在Java虚拟机执行完成后将自动删除

总结

​ 通过对Apace POI框架的SXSSFWorkbook的源码进行了分析,熟悉了整个实现逻辑;针对默认的配置,提供修改方案,如指定临时文件夹路径,设置临时文件压缩等配置。另外,也对SXSSFWorkbook是如何通过滑动窗口实现流式处理的有了大致了解。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值