SXSSFWorkbook.write в FileOutputStream записывает огромные файлы - PullRequest
3 голосов
/ 31 марта 2020

Я пытаюсь использовать 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, который я ожидаю. Я попытался вручную заархивировать файл, и из файла 120 МБ я могу получить его до 9 МБ. Так чего мне не хватает?

Использование: Kotlin и

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

- Обновление 1

У меня сложилось впечатление, что xlsx - это сжатые файлы, содержащие xml данные по существу [1] . То, что 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)
  }
}

Это создает файлы размером 5 МБ каждый, которые при сжатии имеют примерно 439 КБ (?!).

1 Ответ

0 голосов
/ 01 апреля 2020

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_EST_OM используются таблицы общих строк.

Используется: Java 12, apache poi 4.1.2, Ubuntu Linux.

Ни я бы не назвал это огромным для электронной таблицы 2000 строк х 1000 столбцов, ни я Назовите влияние различных настроек большим.

И записи очень хорошо сжаты.

Если вы загляните в ZIP-архив Excel.xlsx, вы найдете несжатый размер xl/worksheets/sheet1.xml 112,380,273 байта при использовании встроенных строк. Несжатый размер xl/sharedStrings.xml равен 138 байтам и содержит только самые основные c XML.

Если используется таблица общих строк, то несжатый размер xl/worksheets/sheet1.xml составляет 68 377 273 байт, а несжатый размер xl/sharedStrings.xml составляет 49 045 байт и содержит 2000 записей.

Если Excel сам сохраняет *.xlsx файлов, он создает файлы, имеющие примерно одинаковый размер файла, когда содержимое равно. Так что Excel сам использует тот же уровень сжатия.

Конечно, можно сжать файлы *.xlsx больше, если снова сохранить Excel.xlsx в архив *.zip. Но это не то, что Excel ожидает от файла *.xlsx.

Microsoft состояния в Каковы преимущества открытых XML форматов? :

Компактные файлы Файлы автоматически сжимаются и может быть до 75 процентов меньше в некоторых случаях. Формат Open XML использует технологию сжатия zip для хранения документов, предлагая потенциальную экономию средств, поскольку уменьшает дисковое пространство, необходимое для хранения файлов, и уменьшает пропускную способность, необходимую для отправки файлов по электронной почте, по сетям и по Inte * 1096. *. Когда вы открываете файл, он автоматически распаковывается. Когда вы сохраняете файл, он автоматически архивируется снова. Вам не нужно устанавливать какие-либо специальные zip-утилиты для открытия и закрытия файлов в Office.

Важные части здесь:

Когда вы открываете файл, он автоматически распаковывается. Когда вы сохраняете файл, он автоматически архивируется снова.

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

Итак, поскольку apache poi создает файлы, которые Excel (Microsoft Office) может открываться напрямую, он использует тот же метод сжатия и уровень сжатия, что и Excel (Microsoft Office) сделает.

...