Обработка большого файла xlsx

мне нужно автоматически поместить все строки в большой (30k+ строк) xlsx-файл.

следующий код через Apache poi работает на небольших файлах, но выходит с OutOfMemoryError на крупных:

Workbook workbook = WorkbookFactory.create(inputStream);
Sheet sheet = workbook.getSheetAt(0);

for (Row row : sheet) {
    row.setHeight((short) -1);
}

workbook.write(outputStream);

обновление: к сожалению, увеличение размера кучи не является вариантом -OutOfMemoryError появляется -Xmx1024m и 30K строк не является верхним пределом.

10 ответов


попробуйте использовать API событий. См.API событий (только HSSF) и XSSF и SAX (Event API) в документации POI для деталей. Пара цитат с этой страницы:

Фип, ГСФ:

API событий новее, чем пользовательский API. Он предназначен для промежуточных разработчиков, которые готовы узнать немного о структурах API низкого уровня. Его относительно простой в использовании, но требует базового понимания частей файла Excel (или готовность учиться). Преимущество заключается в том, что вы можете читать XLS с относительно небольшим объемом памяти.

XSSF:

Если проблема с памятью, то для XSSF вы можете получить базовые XML-данные и обработать их самостоятельно. Это предназначено для промежуточных разработчиков, которые готовы узнать немного о низкоуровневой структуре .xlsx файлы, и кто счастлив обработки XML в java. Относительно простой в использовании, но требуется базовое понимание структуры файла. Преимущество заключается в том, что вы можете читать XLSX-файл с относительно небольшим объемом памяти.

для вывода, один из возможных подходов описан в блоге потоковое xlsx файлы. (В основном, используйте XSSF для создания XML-файла контейнера, а затем передайте фактическое содержимое в виде обычного текста в соответствующую xml-часть zip-архива xlsx.)


резкое улучшение использования памяти может быть сделано с помощью файла вместо потока. (Лучше использовать streaming API, но у Streaming API есть ограничения, см. http://poi.apache.org/spreadsheet/index.html)

Так вместо

Workbook workbook = WorkbookFactory.create(inputStream);

do

Workbook workbook = WorkbookFactory.create(new File("yourfile.xlsx"));

это согласно:http://poi.apache.org/spreadsheet/quick-guide.html#FileInputStream

файлы vs InputStreams

" при открытии книги либо a .xls HSSFWorkbook, или a .xlsx XSSFWorkbook, книга может быть загружена из файла или InputStream. Использование объекта File позволяет снизить потребление памяти, в то время как InputStream требует больше памяти, поскольку он должен буферизировать весь файл."


У меня была такая же проблема с гораздо меньшим количеством строк, но большими строками.

поскольку мне не нужно загружать данные, я обнаружил, что могу использовать SXSSF вместо XSSF.

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

вот ссылка. http://poi.apache.org/spreadsheet/how-to.html#sxssf


Если вы хотите автоматически подогнать или установить стили или записать все строки в большой (30k+ строк) xlsx-файл,используйте SXSSFWorkbook.Вот пример кода, который вам поможет...

SXSSFWorkbook wb = new SXSSFWorkbook();
            SXSSFSheet sheet = (SXSSFSheet) wb.createSheet("writetoexcel");
            Font font = wb.createFont();
                font.setBoldweight((short) 700);
                // Create Styles for sheet.
                XSSFCellStyle Style = (XSSFCellStyle) wb.createCellStyle();
                Style.setFillForegroundColor(new XSSFColor(java.awt.Color.LIGHT_GRAY));
                Style.setFillPattern(XSSFCellStyle.SOLID_FOREGROUND);
                Style.setFont(font);
                //iterating r number of rows
            for (int r=0;r < 30000; r++ )
            {
                Row row = sheet.createRow(r);
                //iterating c number of columns
                for (int c=0;c < 75; c++ )
                {
                    Cell cell = row.createCell(c);
                    cell.setCellValue("Hello"); 
                    cell.setCellStyle(Style);
                }
    }
            FileOutputStream fileOut = new FileOutputStream("E:" + File.separator + "NewTest.xlsx");

Я использовал API событий для файла HSSF (.xls), и я обнаружил ужасное отсутствие документации о порядке записей.


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

http://svn.apache.org/repos/asf/poi/trunk/src/examples/src/org/apache/poi/xssf/eventusermodel/XLSX2CSV.java


Если вы писать в XLSX я нашел улучшение, написав на разные листы одного и того же файла Excel. Вы также можете найти улучшение, написав в разные файлы Excel. Но сначала попробуйте написать на разных листах.


лучший пример для этого описан в следующем потоке переполнения стека: ошибка при чтении больших файлов Excel (xlsx) через Apache POI

фрагмент кода в основном ответе в этом разделе иллюстрирует обертки Apache POI вокруг синтаксического анализа SAX xml и то, как вы можете тривиально перебирать все листы, а затем каждую отдельную ячейку.

код устарел с текущей реализацией API POI Apache, поскольку api endRow() предоставляет текущий номер строки, которая закончила обработку.

с этим фрагментом кода должно быть тривиальным для вашего разбора большой XLSX-файл по ячейкам. Е. Г. для каждого листа; по каждой ячейке строки; строка мероприятие закончилось. Вы можете тривиально создать логику приложения, где в каждой строке вы создаете карту columneName в cellValue.


у меня была такая же проблема с 800,000 ячейками и символами 3M, где XSSF выделяет 1GB кучи!

я использовал Python с openpyxl и numpy чтобы прочитать файл xlsx (из кода Java) и сначала преобразовать его в обычный текст. Затем я загрузил текстовый файл на java. Может показаться, что у него большие накладные расходы, но это действительно быстро.

скрипт python выглядит как

import openpyxl as px
import numpy as np

# xlsx file is given through command line foo.xlsx
fname = sys.argv[1]
W = px.load_workbook(fname, read_only = True)
p = W.get_sheet_by_name(name = 'Sheet1')

a=[]
# number of rows and columns
m = p.max_row
n = p.max_column

for row in p.iter_rows():
    for k in row:
        a.append(k.value)

# convert list a to matrix (for example maxRows*maxColumns)
aa= np.resize(a, [m, n])

# output file is also given in the command line foo.txt
oname = sys.argv[2]
print (oname)
file = open(oname,"w")
mm = m-1
for i in range(mm):
    for j in range(n):
        file.write( "%s " %aa[i,j]  )
    file.write ("\n")

# to prevent extra newline in the text file
for j in range(n):
    file.write("%s " %aa[m-1,j])

file.close()

затем в моем java-коде я написал

try {
  // `pwd`\python_script  foo.xlsx  foo.txt
  String pythonScript =  System.getProperty("user.dir") + "\exread.py ";
  String cmdline = "python " + pythonScript +
                    workingDirectoryPath + "\" + fullFileName + " " + 
                    workingDirectoryPath + "\" + shortFileName + ".txt";
  Process p = Runtime.getRuntime().exec(cmdline);
  int exitCode = p.waitFor();
  if (exitCode != 0) {
    throw new IOException("Python command exited with " + exitCode);
  }
} catch (IOException e) {
  System.out.println( e.getMessage() );
} catch (InterruptedException e) {
  ReadInfo.append(e.getMessage() );
}

после этого, вы получите foo.txt, который похож на foo.XLSX, но в текстовом формате.


Я использовал парсер SAX для обработки структуры XML. Он работает для файлов XLSX.

https://stackoverflow.com/a/44969009/4587961