Java 内存问题时如何修改大型Excel文件
正如标题所述,我有一个很大的Excel文件(>200张),需要向其中添加数据。我不想创建新的单元格,我只想修改现有的单元格Java 内存问题时如何修改大型Excel文件,java,excel,xml,apache-poi,Java,Excel,Xml,Apache Poi,正如标题所述,我有一个很大的Excel文件(>200张),需要向其中添加数据。我不想创建新的单元格,我只想修改现有的单元格 我尝试使用ApachePOI,但即使Xms和Xmx设置为8g,我的应用程序也会耗尽内存。低内存写入的唯一选择似乎是使用SXSSF。问题是它只适用于创建新单元,不允许修改现有单元。我还尝试使用事件API来处理工作表的XML,但它似乎只适用于读取操作。我一直在尝试使用XMLEventWriter,但是我找不到一种方法来访问工作表的XML数据,而这种方法可以用于编写。除了使用XS
我尝试使用ApachePOI,但即使Xms和Xmx设置为8g,我的应用程序也会耗尽内存。低内存写入的唯一选择似乎是使用SXSSF。问题是它只适用于创建新单元,不允许修改现有单元。我还尝试使用事件API来处理工作表的XML,但它似乎只适用于读取操作。我一直在尝试使用XMLEventWriter,但是我找不到一种方法来访问工作表的XML数据,而这种方法可以用于编写。除了使用XSSFReader之外,还有其他方法访问Excel文件的XML数据吗?正如上面的评论所述,使用纯
XML
读取和写入Office Open XML
电子表格并不是万能的解决方案。每个Excel
工作簿都需要自己的代码,这取决于它的结构和需要更改的内容
这是因为ApachePOI的高级类提供了一个元级别来避免这种情况。但这需要内存才能工作。对于非常大的工作簿,它需要大量内存。为了避免通过直接操作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();
}
}
这将比ApachePOI的
XSSF
类消耗更少的内存。但是,如前所述,它只适用于这样的Excel
工作簿,它的第一张工作表在a
列中有字符串,在B
列中有数字,如果问题只是将所有200多张工作表一起解析为XSSFSheet
s,那么您可以覆盖XSSFWorkbook.parseSheet
,如中所示。使用此选项,您只能将一个或部分工作表解析为XSSFSheet
。当然,有时甚至这也不可能,因为所有的工作表都是相互依赖的。如果对于XSSFSheet
来说,即使只有一个工作表太大,那么使用高级apache poi
类也是不可能的。然后,只能使用纯XML
方法。我已经展示了如何在阅读和写作中做到这一点。问题主要来自写作。使用POI,我可以打开工作簿并修改单元格,但应用程序在最后一次调用workbook.write()时崩溃。我用StAX写了一些代码,但我不知道如何访问这些工作表进行写作。通过使用XSSFReader生成InputStream,然后使用InputStream创建XML读取器,可以轻松访问这些工作表。但是,没有生成用于写入的Outputstream的类。写入一个常规的.xml文件很简单,但我不知道如何将Excel文档传递给xml编写器。我已经使用StAX
链接了我的示例。它们包含用于读写的代码。但是,当然,这些都不能用于读写各种Excel
文件。每个Excel
工作簿都需要自己的代码,这取决于它的结构和应更改的内容。根据我所知,以书写为特征的示例仅在文档末尾这样做。我正在尝试修改现有的元素/单元格。这对StAX可能吗?我用你的例子作为灵感制作了一些有用的东西,谢谢你的帮助。我唯一要做的就是找出如何强制重新计算工作表中的公式。workbook.xml中是否有这样做的标志?在此处找到了我上一个问题(forcellcalc)的答案:。希望这能帮助别人