SXSSFWorkbook.write to FileOutputStream 写入大文件

2023-12-27

我正在尝试使用 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] https://rzymek.github.io/post/excel-zip64/。 POI 通过 XSSFWorkbook 和 SXSSFWorkbook 输出的内容至少可以压缩 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默认使用内联字符串而不是共享字符串表。这意味着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) of SXSSFWorkbook设置另一个压缩级别。但这也不会对生成的文件大小产生太大影响*.xlsx.

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

 }
}

这导致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 和共享字符串表时。

Used: Java 12, apache poi 4.1.2, Ubuntu Linux.

对于 2,000 行 x 1,000 列的电子表格,我既不认为其巨大,也不认为不同设置的影响很大。

而且条目压缩得非常好。

如果你调查Excel.xlsxZIP 存档,您会发现未压缩的大小xl/worksheets/sheet1.xml使用内联字符串时为 112,380,273 字节。未压缩的大小为xl/sharedStrings.xml则为 138 字节,仅包含非常基本的 XML。

如果使用共享字符串表,则未压缩的大小为xl/worksheets/sheet1.xml是 68,377,273 字节,未压缩的大小为xl/sharedStrings.xml大小为 49,045 字节,包含 2,000 个条目。

If Excel本身保存*.xlsx文件,当内容相等时,它会创建具有大致相同文件大小的文件。所以Excel本身使用相同的压缩级别。

当然可以压缩*.xlsx存储时文件较多Excel.xlsx into a *.zip再次存档。但那不会是什么Excel期望成为一个*.xlsx file.

Microsoft状态于Open XML 格式有哪些好处? https://support.office.com/en-gb/article/open-xml-formats-and-file-name-extensions-5200d93c-3449-4380-8e11-31ef14555b18#bm2:

压缩文件文件会自动压缩,在某些情况下最多可缩小 75%。 Open XML 格式使用 zip 压缩技术来存储文档,提供潜在的成本 节省,因为它减少了存储文件所需的磁盘空间 减少通过电子邮件发送文件所需的带宽 网络,以及整个互联网。当你打开一个文件时,它是 自动解压。当您保存文件时,它会自动 再次拉上拉链。您无需安装任何特殊的 zip 实用程序即可 在 Office 中打开和关闭文件。

这里重要的部分是:

当您打开文件时,它会自动解压缩。当您保存一个 文件,它会再次自动压缩。

这意味着,如果apache poi将使用其他方法或压缩级别来压缩文件Microsoft Office本身,那么Microsoft Office无法对文件执行此操作apache poi已经创建了。

所以,自从apache poi创建文件Excel (Microsoft Office)可以直接打开,使用与Excel (Microsoft Office) 会做。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SXSSFWorkbook.write to FileOutputStream 写入大文件 的相关文章

  • 任务“:app:dexDebug”执行失败

    我目前正在处理我的项目 我决定将我的 Android Studio 更新到新版本 但在我导入项目后 它显示如下错误 Information Gradle tasks app assembleDebug app preBuild UP TO
  • 为什么 Kotlin 数据类可以在 Gson 的不可空字段中包含空值?

    在 Kotlin 中你可以创建一个data class data class CountriesResponse val count Int val countries List
  • 将链接对象转换为流或集合

    我想迭代堆栈跟踪 堆栈跟踪由可抛出对象组成 其 getCause 返回下一个可抛出对象 最后一次调用 getCause 返回 null 示例 a gt b gt null 我尝试使用 Stream iterable 这会导致 NullPoi
  • 无法加载 jar 文件的主类

    我使用 Eclipse IDE 开发了一个应用程序 创建应用程序后 我以 jar 格式导出项目 当我尝试运行此 jar 文件时 出现错误 无法加载主类 请帮忙 当您将项目导出为 jar 时 请参阅此所以问题 https stackoverf
  • MI设备中即使应用程序被杀死,如何运行后台服务

    您好 我正在使用 alaram 管理器运行后台服务 它工作正常 但对于某些 mi 设备 后台服务无法工作 我使用了服务 但它无法工作 如何在 mi 中运行我的后台服务 MI UI有自己的安全选项 所以你需要的不仅仅是上面提到的粘性服务 你需
  • Java:从元素创建 DOM 元素,而不是文档

    如您所知 在 Java 中创建 Dom 元素的正确方法是执行以下操作 import org w3c dom Document import org w3c dom Element Document d Element e e d creat
  • Android - 除了普通 SSL 证书之外还验证自签名证书

    我有一个通过 SSL 调用 Web 服务的 Android 应用程序 在生产中 我们将拥有由受信任的 CA 签名的普通 SSL 证书 但是 我们需要能够支持自签名证书 由我们自己的 CA 签名 我已经成功实施了接受自签名证书的建议解决方案
  • 如何将 XMP XML 块序列化为现有的 JPEG 图像?

    我有许多 JPEG 图像 其中包含损坏的 XMP XML 块 我可以轻松修复这些块 但我不确定如何将 固定 数据写回图像文件 我目前正在使用 JAVA 但我愿意接受任何能让这项任务变得容易的事情 这是目标关于 XMP XML 的另一个问题
  • 6:需要显示BuyFlow UI

    There is a problem when i am click on payWithGoogle Button I am implementing Google Pay in my Android Application and wh
  • 使用 Guava 联合两个 ImmutableEnumSets

    我想联合两个ImmutableEnumSets来自番石榴 这是我的尝试 public final class OurColors public enum Colors RED GREEN BLUE YELLOW PINK BLACK pub
  • Java:VM 如何在 32 位处理器上处理 64 位“long”

    JVM 如何在 32 位处理器上处理 64 位的原始 long 在多核 32 位机器上可以并行利用多个核心吗 64 位操作在 32 位机器上慢了多少 它可能使用多个核心来运行不同的线程 但不会并行使用它们进行 64 位计算 64 位长基本上
  • tomcat 过滤所有 web 应用程序

    问题 我想对所有网络应用程序进行过滤 我创建了一个过滤器来监视对 apache tomcat 服务器的请求 举例来说 它称为 MyFilter 我在 netbeans 中创建了它 它创建了 2 个独立的目录 webpages contain
  • 让JScrollPane控制多个组件

    对于我的应用程序 我正在设计一个脚本编辑器 目前我有一个JPanel其中包含另一个JPanel保存行号 位于左侧 以及JTextArea用于允许用户输入代码 位于右侧 目前 我已经实施了JScrollPane on the JTextAre
  • 如何为 Jackson 编写一个包罗万象的(反)序列化器

    当您提前知道类型时 编写自定义序列化器非常容易 例如 MyType一个人可以写一个MyTypeSerializer extends StdSerializer
  • ExceptionHandler 不适用于 Throwable

    我们的应用程序是基于 Spring MVC 的 REST 应用程序 我正在尝试使用 ExceptionHandler 注释来处理所有错误和异常 I have ExceptionHandler Throwable class public R
  • 如何在android sdk上使用PowerMock

    我想为我的 android 项目编写一些单元测试和仪器测试 然而 我遇到了一个困扰我一段时间的问题 我需要模拟静态方法并伪造返回值来测试项目 经过一些论坛的调查 唯一的方法是使用PowerMock来模拟静态方法 这是我的 gradle 的一
  • struts 教程或示例

    我正在尝试在 Struts 中制作一个登录页面 这个想法是验证用户是否存在等 然后如果有错误 则返回到登录页面 错误显示为红色 典型的登录或任何表单页面验证 我想知道是否有人知道 Struts 中的错误管理教程 我正在专门寻找有关的教程 或
  • 从一个文本文件中获取数据并将其移动到新的文本文件

    我有一个文件 里面有数据 在我的主要方法中 我读入文件并关闭文件 我调用另一种方法 在原始文件的同一文件夹内创建一个新文件 所以现在我有两个文件 原始文件和通过我调用的方法生成的文件 我需要另一种方法 从原始文件中获取数据并将其写入创建的新
  • Java中获取集合的幂集

    的幂集为 1 2 3 is 2 3 2 3 1 2 1 3 1 2 3 1 假设我有一个Set在爪哇中 Set
  • 为什么 BufferedWriter 不写入文件?

    我有这个代码 String strings Hi You He They Tetrabenzene Caaorine Calorine File file new File G words txt FileWriter fWriter Bu

随机推荐