Инструмент Excel — исследование SXSSFWorkbook и анализ нехватки памяти

Java

В сегодняшнем проекте необходим экспорт в Excel. Я видел, что мои коллеги использовали SXSSFWorkbook. Раньше я не использовал этот компонент. Поскольку он используется на этот раз, давайте кратко проанализируем его. POI предоставляет HSSF, XSSF и SXSSF три способа работы с Excel. Их отличия заключаются в следующем:

HSSF:是操作Excel97-2003版本,扩展名为.xls。
XSSF:是操作Excel2007版本开始,扩展名为.xlsx。
SXSSF:是在XSSF基础上,POI3.8版本开始提供的一种支持低内存占用的操作方式,扩展名为.xlsx。

В этой статье основное внимание уделяется анализу того, как SXSSF поддерживает низкое использование памяти. Вывод первый:SXSSF 指定了rowAccessWindowSize ,每个sheet 对应一个临时文件,当行数大于rowAccessWindowSize 时,就会向临时文件中flush, 这样就保证了内存的低占用率。当行创建完,直接从临时文件中写入到Excel中。Одно замечание:像单元格合并类似的操作是纯内存操作,如果项目中想一次合并多行时,要注意随时观察自己机器内容的使用情况,避免出现OOM。

1 в демо

        // 内存中保持100条数据, 超出的部分刷新到磁盘上
        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++){
                // 创建行,在这儿根据当前行数跟rowAccessWindowSize 比较,来决定从内存写入文件中。
                Cell cell = row.createCell(cellnum);
                String address = new CellReference(cell).formatAsString();
                cell.setCellValue(address);
            }

        }

        // rownum < 900 的数据被刷新到磁盘,不能被随机访问
        for(int rownum = 0; rownum < 900; rownum++){
            Assert.assertNull(sh.getRow(rownum));
        }

        // 最后的100条数据仍然在内存中,可以随机访问
        for(int rownum = 900; rownum < 1000; rownum++){
            Assert.assertNotNull(sh.getRow(rownum));
        }
        // 从临时文件写入Excel 文件
        FileOutputStream out = new FileOutputStream("d:\\sxssf.xlsx");
        wb.write(out);
        out.close();

        // 从磁盘上释放临时文件
        wb.dispose();

2 основных пункта анализа

2.1 Создание рабочей книги SXSSF

Как показано в демо,SXSSFWorkbook wb = new SXSSFWorkbook(100);Размер rowAccessWindowSize указан как 100, то есть Данные строки rowAccessWindowSize кэшируются в памяти. Когда количество строк превышает rowAccessWindowSize, оно вводится из памяти во временный файл.

Временные файлы создаются в2.2 创建SheetПоговорим об этом частично. Если порог превышен, временный файл сбрасывается в2.3 创建rowЧасть объяснения.

2.2 Создать лист

Как показано в демо,Sheet sh = wb.createSheet();Созданный лист, Затем в процессе создания основной функцией является создание временного файла. Один временный файл на лист. Без лишних слов давайте взглянем на реализацию createSheet.

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

Ядром createAndRegisterSXSSFSheet являетсяsxSheet = new SXSSFSheet(this, xSheet);. Итак, давайте посмотрим на эту функцию:

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);
    }

Основной логикой в ​​createSheetDataWriter является SheetDataWriter. См. createTempFile, где создается временный файл.

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

Что касается временных файлов:

Префикс: poi-sxssf-лист Суффикс: .xml Путь хранения: код выглядит следующим образом

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);
    }

2.3 Создать строку

Когда файл записывается из памяти? при создании строки. Тогда давайте посмотрим на код:

public SXSSFRow createRow(int rownum) {
        int maxrow = SpreadsheetVersion.EXCEL2007.getLastRowIndex();
        if (rownum >= 0 && rownum <= 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.");
            } else {
                SXSSFRow newRow = new SXSSFRow(this);
                this._rows.put(rownum, newRow);
                this.allFlushed = false;
                // 这儿进行了判断,如果当前行数大于randomAccessWindowSize ,则flushRows
                if (this._randomAccessWindowSize >= 0 && this._rows.size() > this._randomAccessWindowSize) {
                    try {
                        this.flushRows(this._randomAccessWindowSize);
                    } catch (IOException var5) {
                        throw new RuntimeException(var5);
                    }
                }

                return newRow;
            }
        } else {
            throw new IllegalArgumentException("Invalid row number (" + rownum + ") outside allowable range (0.." + maxrow + ")");
        }
    }

Логика суждения здесьif (this._randomAccessWindowSize >= 0 && this._rows.size() > this._randomAccessWindowSize).

Следующие части не имеют ничего общего с низким использованием памяти, а просто анализируют несколько шагов, фактически используемых в проекте.

2.4 Запись окончательного Excel из временного файла

Запись в Excel в основном выполняется вworkbook.write(out). Посмотрите на код:

public void write(OutputStream stream) throws IOException {
        this.flushSheets(); // 把最后不足randomAccessWindowSize 的行数 写入sheet临时文件。
        File tmplFile = TempFile.createTempFile("poi-sxssf-template", ".xlsx"); // 创建了一个tmplFile临时文件,不是sheet的临时文件哈

        boolean deleted;
        try {
            FileOutputStream os = new FileOutputStream(tmplFile);
            Throwable var5 = null;

            try {
               // 这儿将workbook 中所有的数据都写入刚刚创建的tmplFile临时文件中。
                this._wb.write(os);
            } 
           ...
            ZipSecureFile zf = new ZipSecureFile(tmplFile);  
            var5 = null;

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

                try {
                  // 将tmplFile 临时文件写入到目标Excel中。
                    this.injectData(source, stream);
                } 
                ...
        } finally {
            // 删除tmplFile临时文件。 注意不是sheet的临时文件哈。
            deleted = tmplFile.delete();
        }

        if (!deleted) {
            throw new IOException("Could not delete temporary file after processing: " + tmplFile);
        }
    }

Основная логика очень проста:

(1) Сначала запишите данные, которые меньше randomAccessSize в памяти, во временный файл листа.

(2) Запишите все данные в книге (то есть временные файлы с несколькими листами) в только что созданный временный файл tmpl.

(3) Запишите данные временного файла tmpl в целевой файл.

2.5 Удалить временные файлы

workbook.dispose();Логика здесь.

public boolean dispose() {
        boolean success = true;
        Iterator var2 = this._sxFromXHash.keySet().iterator();
        // 逐个遍历多个sheet
        while(var2.hasNext()) {
            SXSSFSheet sheet = (SXSSFSheet)var2.next();

            try {
                // 这儿的核心是dispose.
                success = sheet.dispose() && success;
            } catch (IOException var5) {
                logger.log(5, new Object[]{var5});
                success = false;
            }
        }

        return success;
    }

Основная логика состоит в том, чтобы пройти несколько листов, а затем выполнить удаление на каждом листе Логика удаления состоит в том, чтобы сначала сбросить, а затем удалить временный файл листа, если на листе нет выходного файла.

2.6 Об операции объединения ячеек

Как объединить ячейки:

  CellRangeAddress region0 = new CellRangeAddress(rowNum, rowNum+1, column, column);
  sheet.addMergedRegion(region0);

Здесь просто слияние по rowNum и столбцу.

private int addMergedRegion(CellRangeAddress region, boolean validate) {
        if (region.getNumberOfCells() < 2) {
            throw new IllegalArgumentException("Merged region " + region.formatAsString() + " must contain 2 or more cells");
        } else {
            region.validate(SpreadsheetVersion.EXCEL2007);
            if (validate) {
                this.validateArrayFormulas(region);
                this.validateMergedRegions(region);
            }

            CTMergeCells ctMergeCells = this.worksheet.isSetMergeCells() ? this.worksheet.getMergeCells() : this.worksheet.addNewMergeCells();
            CTMergeCell ctMergeCell = ctMergeCells.addNewMergeCell();
            ctMergeCell.setRef(region.formatAsString());
            return ctMergeCells.sizeOfMergeCellArray();
        }
    }

3 Резюме

В этой статье объединен инструмент Excel, используемый в проекте — SXSSFWorkbook, для краткого пояснения. И проанализировал часть SXSSFWorkbook, занимающую мало памяти. Надеюсь, это поможет вам~ Эта статья одновременно публикуется в Цзяньшуу-у-у. Краткое описание.com/afraid/18046332No…

4 ссылки

Отличия HSSF, XSSF и SXSSF и оптимизация экспорта в Excelблог woo woo woo.cn на.com/photographed/afraid/74…

Экспорт файла EXCEL на основе потока, анализ исходного кода SXSSFWorkbookwoo woo Краткое описание.com/afraid/no 80 ah 20 no 81…

#5 Другое

(1) При чтении чисел Excel по умолчанию отображается «.0», как это решить?Нет .OSCHINA.net/cross, чтобы узнать…