Java SXSSFWorkbook.write to FileOutputStream写入大量文件

Java SXSSFWorkbook.write to FileOutputStream写入大量文件,java,kotlin,apache-poi,openxml,Java,Kotlin,Apache Poi,Openxml,我正在尝试使用SXSSFWorkbook从头开始编写Excel电子表格 wb = SXSSFWorkbook(500) wb.isCompressTempFiles = true sh = streamingWorkbook.createSheet(t.getMessage("template.sheet.name")) 一切正常,但当我调用最终代码时: val out = FileOutputStream(localPath) wb.wri

我正在尝试使用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数据的压缩文件。通过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(?!)。

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

sxsfWorkbook
,以及所有其他
Office Open XML
格式化文件,
apache poi
创建的文件,都是使用
org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream压缩的。它使用deflate作为压缩算法,并使用
Deflater.DEFAULT\u compression
作为默认压缩级别。您可以覆盖
sxsfWorkbook
受保护的ZipArchiveOutputStream createArchiveOutputStream(OutputStream out)
,以设置另一个压缩级别。但这也不会对生成的
*.xlsx
的文件大小产生太大影响

示例
Java
code:

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

 }
}
导入java.io.File;
导入java.io.OutputStream;
导入java.io.FileOutputStream;
导入org.apache.poi.ss.usermodel.*;
导入org.apache.poi.xssf.streaming.SXSSFWorkbook;
导入org.apache.commons.compress.archivers.zip.Zip64Mode;
导入org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
导入java.util.zip.Deflater;
类CreateSXSFDDifferentCompression{
静态SXSSFWorkbook创建SXSSFWorkbook(int压缩级别,int行访问窗口大小,
布尔压缩文件,布尔使用共享字符串稳定){
SXSSFWorkbook工作簿=空;
if(压缩级别!=平减器。默认压缩){
工作簿=新的SXSSFWorkbook(null、RowAccessWindowsSize、compressTmpFiles、useSharedStringsTable){
受保护的ZipArchiveOutputStream createArchiveOutputStream(OutputStream out){
ZipArchiveOutputStream zos=新ZipArchiveOutputStream(输出);
zos.setUseZip64(Zip64Mode.AsNeeded);
zos.设置水平(压缩水平);
返回zos;
}    
}; 
}否则{
工作簿=新的SXSSFWorkbook(null、RowAccessWindowsSize、compressTmpFiles、useSharedStringsTable);
}
返回工作簿;
}
公共静态void main(字符串[]args)引发异常{
SXSSFWorkbook工作簿=空;
//使用Deflater.DEFAULT\u压缩和内联字符串
//工作簿=CreateSXSFWWorkbook(Deflater.DEFAULT\u压缩,500,真,假);
//使用Deflater.DEFAULT\u压缩和共享字符串表
//工作簿=CreateSXSFWWorkbook(Deflater.DEFAULT\u压缩,500,true,true);
//使用Deflater.BEST_压缩和内联字符串
工作簿=CreateSXSFWWorkbook(Deflater.BEST_压缩,500,真,假);
//使用Deflater.BEST_压缩和共享字符串表
//工作簿=CreateSXSFWWorkbook(Deflater.BEST_压缩,500,真,真);
int ROWS_COUNT=2000;
int COLS_COUNT=1000;
工作表=工作簿.createSheet(“测试表1”);

对于(int i=1;我认为您应该在这里显示更多的代码…需要担心的是缺少对
wb.close()
的调用,但在使用
SXSSFWorkbook
时可能不需要它,而我在
XSSFWorkbook
中发现它是必要的。我已经尝试过了,wb.close似乎没有什么作用:(无法仅复制简单的代码片段。
sxsfWorkbook
默认使用内联字符串而不是共享字符串表。这意味着
sxsfWorkbook
直接在工作表中写入文本,即使它是相同文本的多倍。
XSSFWorkbook
和Excel的GUI都使用共享字符串表其中文本获取索引,同一文本只存储一次,然后在工作表中使用索引。但这不会对生成的
*.xlsx
的文件大小产生太大的影响。我正在创建只有少数字符串类型的大文件,以便产生巨大的影响!同时发现:但这有点不相关。“巨大”标题中所指的是包含2500多列和10k多行的工作表,这些工作表的大小为120+MB。与外部压缩文件(将文件大小压缩到~10MB)相比,POI和Excel所做的任何压缩似乎都远远不够。