SXSSFWorkbook.write到FileOutputStream写入巨大的文件

问题描述 投票:3回答:1

我正在尝试使用SXSSFWorkbook从头开始编写Excel电子表格。

      wb = SXSSFWorkbook(500)
      wb.isCompressTempFiles = true
      sh = streamingWorkbook.createSheet(t.getMessage("template.sheet.name"))

一切都很好,但是当我调用最终代码时:

    val out = FileOutputStream(localPath)
    wb.write(out)
    out.close()
    // dispose of temporary files backing this workbook on disk
    wb.dispose()

我得到了一个巨大的Excel文件,而不是我期望的压缩XLSX。我尝试手动压缩文件,并且可以将文件从120MB压缩到9MB。那我想念什么?

使用:Kotlin和

    implementation group: 'org.apache.poi', name: 'poi-ooxml', version: '4.1.2'  // For `.xlsx` files

-更新1

我的印象是xlsx是本质上包含XML数据的压缩文件[1]。通过XSSFWorkbook和SXSSFWorkbook输出的POI至少可以压缩10个数量级。我已使用此简单代码演示:

fun main() {
  val workbook = XSSFWorkbook()
  writeRowsAndSave(workbook, "test.xlsx")
  workbook.close()

  val streamingWorkbook = SXSSFWorkbook(IN_MEMORY_ROWS_WINDOW_SIZE)
  streamingWorkbook.isCompressTempFiles = true
  writeRowsAndSave(streamingWorkbook, "test-streaming.xlsx")
  streamingWorkbook.dispose()
}

private fun writeRowsAndSave(workbook: Workbook, fileName: String) {
  val ROWS_COUNT = 2_000
  val COLS_COUNT = 1_000

  val sheet = workbook.createSheet("Test Sheet 1")
  for (i in 1..ROWS_COUNT) {
    val row = sheet.createRow(i)
    println("Row $i")
    for(j in 1..COLS_COUNT) {
        row.createCell(j).setCellValue("Test $i")
    }
  }

  FileOutputStream("./$fileName").use {
      workbook.write(it)
  }
}

这将产生5MB的文件,每个文件在压缩后大约有439KB(?!)。

java kotlin apache-poi openxml
1个回答
0
投票

SXSSFWorkbook默认使用内联字符串而不是共享字符串表。这意味着SXSSFWorkbook即使在同一文本中多次写入文本,也直接在工作表中写入文本。 XSSFWorkbook和Excel的GUI都使用共享字符串表,其中文本获取索引,同一文本仅存储一次,然后在工作表中使用索引。但这对结果*.xlsx的文件大小没有太大影响。

SXSSFWorkbook以及Office Open XML创建的所有其他apache poi格式文件都已使用org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream压缩。该操作使用deflate作为压缩算法,并使用Deflater.DEFAULT_COMPRESSION作为默认压缩级别。可以覆盖protected ZipArchiveOutputStream createArchiveOutputStream(OutputStream out)SXSSFWorkbook来设置另一压缩级别。但这也不会对结果*.xlsx的文件大小产生太大影响。

示例Java代码:

import java.io.File;
import java.io.OutputStream;
import java.io.FileOutputStream;

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;

import org.apache.commons.compress.archivers.zip.Zip64Mode;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import java.util.zip.Deflater;

class CreateSXSSFDifferentCompression {

 static SXSSFWorkbook createSXSSFWorkbook(int compressionLevel, int rowAccessWindowSize, 
                                          boolean compressTmpFiles, boolean useSharedStringsTable) {
  SXSSFWorkbook workbook = null;
  if (compressionLevel != Deflater.DEFAULT_COMPRESSION) {
   workbook = new SXSSFWorkbook(null, rowAccessWindowSize, compressTmpFiles, useSharedStringsTable) {
    protected ZipArchiveOutputStream createArchiveOutputStream(OutputStream out) {
     ZipArchiveOutputStream zos = new ZipArchiveOutputStream(out);
     zos.setUseZip64(Zip64Mode.AsNeeded);  
     zos.setLevel(compressionLevel);
     return zos;
    }    
   }; 
  } else {
   workbook = new SXSSFWorkbook(null, rowAccessWindowSize, compressTmpFiles, useSharedStringsTable);
  }
  return workbook;
 }

 public static void main(String[] args) throws Exception {

  SXSSFWorkbook workbook = null;

  // uses Deflater.DEFAULT_COMPRESSION and inline strings
  //workbook = createSXSSFWorkbook(Deflater.DEFAULT_COMPRESSION, 500, true, false); 

  // uses Deflater.DEFAULT_COMPRESSION and shared strings table
  //workbook = createSXSSFWorkbook(Deflater.DEFAULT_COMPRESSION, 500, true, true); 

  // uses Deflater.BEST_COMPRESSION and inline strings
  workbook = createSXSSFWorkbook(Deflater.BEST_COMPRESSION, 500, true, false); 

  // uses Deflater.BEST_COMPRESSION and shared strings table
  //workbook = createSXSSFWorkbook(Deflater.BEST_COMPRESSION, 500, true, true); 

  int ROWS_COUNT = 2000;
  int COLS_COUNT = 1000;

  Sheet sheet = workbook.createSheet("Test Sheet 1");
  for (int i = 1 ; i <= ROWS_COUNT; i++) {
   Row row = sheet.createRow(i);
   //System.out.println("Row " + i);
   for(int j = 1; j <= COLS_COUNT; j++) {
    row.createCell(j).setCellValue("Test " + i);
   }
  }

  FileOutputStream out = new FileOutputStream("./Excel.xlsx");
  workbook.write(out);
  out.close();
  workbook.close();
  workbook.dispose();

  File file = new File("./Excel.xlsx");
  System.out.println(file.length());

 }
}

这导致Excel.xlsx文件大小为:

5,031,034字节当使用Deflater.DEFAULT_COMPRESSION和内联字符串时。

4,972,663字节当使用Deflater.DEFAULT_COMPRESSION和共享字符串表时。

4,972,915字节当使用Deflater.BEST_COMPRESSION和内联字符串时。

和4,966,749字节当使用Deflater.BEST_COMPRESSION和共享字符串表时。

使用:Java 12apache poi 4.1.2Ubuntu Linux

对于拥有2,000行x 1,000列的电子表格,我都不会说太大,也不会说不同设置的影响会很大。

如果Excel本身保存*.xlsx文件,则当内容相等时,它将创建具有大致相同文件大小的文件。因此Excel本身使用相同的压缩级别。当然,当将*.xlsx文件存储到*.zip归档文件中时,可以更多地压缩它们。但是我怀疑Excel是否能够在不先解压缩的情况下打开它们。因此,由于apache poi创建的文件可以直接打开Excel,因此它使用的压缩级别与Excel相同。

© www.soinside.com 2019 - 2024. All rights reserved.