Как изменить большой файл Excel, когда проблема с памятью - PullRequest
1 голос
/ 24 июня 2019

Как гласит заголовок, у меня есть большой файл Excel (> 200 листов), к которому мне нужно добавить данные.Я не хочу создавать новые ячейки, я только хочу изменить существующие.

Я пытался использовать Apache Poi, но моему приложению не хватает памяти, даже если для Xms и Xmx установлено значение 8g.Единственная опция для записи с небольшим объемом памяти, по-видимому, с SXSSF.Проблема в том, что он работает только для создания новых ячеек и не позволяет изменять существующие.Я также попытался использовать API-интерфейс событий для обработки XML-кода листа, но, похоже, он работает только для операций чтения.Я пытался использовать XMLEventWriter, но я не могу найти способ доступа к XML-данным листов, который подходит для записи.Есть ли способ получить доступ к XML-данным файла Excel, кроме XSSFReader?

1 Ответ

1 голос
/ 25 июня 2019

Как сказано в комментариях выше, никто не подходит для всех решений, использующих чистые XML чтения и записи Office Open XML электронных таблиц.Для каждой Excel рабочей книги нужен собственный код, зависящий от ее структуры и от того, какой контент должен быть изменен.

Это потому, что классы высокого уровня apache poi предоставляют метауровень, чтобы избежать этого.Но для этого нужна память.А для очень больших рабочих книг требуется много памяти.Чтобы избежать потребления памяти путем непосредственного манипулирования XML, этот мета-уровень не может использоваться.Таким образом, нужно знать структуру XML листа и значение используемых элементов XML.

Итак, если у нас есть книга Excel с первым листом, содержащим строки в столбце A и числа в столбце B, то мы можем изменить каждую пятую строку, используя StAX для манипулирования XML непосредственно используя следующий код:

import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackagePart;

import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;

import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTRst;

import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

import javax.xml.namespace.QName;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;

import java.util.regex.Pattern;

class StaxReadAndChangeTest {

 public static void main(String[] args) throws Exception {
  File file = new File("ReadAndWriteTest.xlsx");
  OPCPackage opcpackage = OPCPackage.open(file);

  //since there are strings in the sheet data, we need the SharedStringsTable
  PackagePart sharedstringstablepart = opcpackage.getPartsByName(Pattern.compile("/xl/sharedStrings.xml")).get(0);
  SharedStringsTable sharedstringstable = new SharedStringsTable();
  sharedstringstable.readFrom(sharedstringstablepart.getInputStream());

  //get first worksheet
  PackagePart sheetpart = opcpackage.getPartsByName(Pattern.compile("/xl/worksheets/sheet1.xml")).get(0);

  //get XML reader and writer            
  XMLEventReader reader = XMLInputFactory.newInstance().createXMLEventReader(sheetpart.getInputStream());
  XMLEventWriter writer = XMLOutputFactory.newInstance().createXMLEventWriter(sheetpart.getOutputStream());

  XMLEventFactory eventFactory = XMLEventFactory.newInstance();

  int rowsCount = 0;
  int colsCount = 0;
  boolean cellAfound = false;
  boolean cellBfound = false;

  while(reader.hasNext()){ //loop over all XML in sheet1.xml
   XMLEvent event = (XMLEvent)reader.next();
   if(event.isStartElement()) {
    StartElement startElement = (StartElement)event;
    QName startElementName = startElement.getName();
    if(startElementName.getLocalPart().equalsIgnoreCase("row")) { //start element of row
     rowsCount++;
     colsCount = 0;
    } else if (startElementName.getLocalPart().equalsIgnoreCase("c")) { //start element of cell
     colsCount++;
     cellAfound = false;
     cellBfound = false;
     if (rowsCount % 5 == 0) { // every 5th row
      if (colsCount == 1) { // cell A
       cellAfound = true;
      } else if (colsCount == 2) { // cell B
       cellBfound = true;
      } 
     }
    } else if (startElementName.getLocalPart().equalsIgnoreCase("v")) { //start element of value
     if (cellAfound) {
      // create new rich text content for cell A
      CTRst ctstr = CTRst.Factory.newInstance();
      ctstr.setT("changed String Value A" + (rowsCount));
      //int sRef = sharedstringstable.addEntry(ctstr);
      int sRef = sharedstringstable.addSharedStringItem(new XSSFRichTextString(ctstr));
      // set the new characters for A's value in the XML
      if (reader.hasNext()) {
       writer.add(event); // write the old event
       event = (XMLEvent)reader.next(); // get next event - should be characters
       if (event.isCharacters()) {
        Characters value = eventFactory.createCharacters(Integer.toString(sRef));
        event = value;
       } 
      }        
     } else if (cellBfound) {
      // set the new characters for B's value in the XML
      if (reader.hasNext()) {
       writer.add(event); // write the old event
       event = (XMLEvent)reader.next(); // get next event - should be characters
       if(event.isCharacters()) { 
        double oldValue = Double.valueOf(((Characters)event).getData()); // old double value
        Characters value = eventFactory.createCharacters(Double.toString(oldValue * rowsCount));
        event = value;         
       }         
      }
     }
    }
   }
   writer.add(event); //by default write each read event
  }
  writer.flush();

  //write the SharedStringsTable
  OutputStream out = sharedstringstablepart.getOutputStream();
  sharedstringstable.writeTo(out);
  out.close();
  opcpackage.close();

 }
}

Это будет намного меньше занимать память, чем классы apache poi XSSF.Но, как уже было сказано, он работает только для такой книги Excel, в которой первый лист содержит строки в столбце A и числа в столбце B.

...