通过 Apache POI 读取大型 Excel 文件 (xlsx) 时出错
Posted
技术标签:
【中文标题】通过 Apache POI 读取大型 Excel 文件 (xlsx) 时出错【英文标题】:Error While Reading Large Excel Files (xlsx) Via Apache POI 【发布时间】:2012-10-22 22:10:00 【问题描述】:我正在尝试通过 Apache POI 读取大型 excel 文件 xlsx,例如 40-50 MB。我的内存不足异常。当前堆内存为 3GB。
我可以毫无问题地读取较小的 Excel 文件。我需要一种方法来读取大型 excel 文件,然后通过 Spring excel 视图将它们作为响应返回。
public class FetchExcel extends AbstractView
@Override
protected void renderMergedOutputModel(
Map model, HttpServletRequest request, HttpServletResponse response)
throws Exception
String fileName = "SomeExcel.xlsx";
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
OPCPackage pkg = OPCPackage.open("/someDir/SomeExcel.xlsx");
XSSFWorkbook workbook = new XSSFWorkbook(pkg);
ServletOutputStream respOut = response.getOutputStream();
pkg.close();
workbook.write(respOut);
respOut.flush();
workbook = null;
response.setHeader("Content-disposition", "attachment;filename=\"" +fileName+ "\"");
我一开始是使用XSSFWorkbook workbook = new XSSFWorkbook(FileInputStream in);
但这对于每个 Apache POI API 来说都是昂贵的,所以我改用 OPC 封装方式,但效果还是一样。我不需要解析或处理文件,只需读取并返回即可。
【问题讨论】:
试试 SXSSF poi.apache.org/spreadsheet/index.html 我需要一个例子。我在网上搜索,但找不到通过 SXSSF 阅读大表格的示例,否则一开始就不会问这个问题。 @jamesT 你运行这个选项了吗? -Xms1024M -Xmx2048M 没关系,SXSSF 仅用于写入大量数据。给 JVM heap 更多的内存,避免这种粗鲁的 cmets。 您尝试过 ODBC 连接吗?也许会是更好的方法 【参考方案1】:这是一个使用 sax 解析器读取大型 xls 文件的示例。
public void parseExcel(File file) throws IOException
OPCPackage container;
try
container = OPCPackage.open(file.getAbsolutePath());
ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(container);
XSSFReader xssfReader = new XSSFReader(container);
StylesTable styles = xssfReader.getStylesTable();
XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) xssfReader.getSheetsData();
while (iter.hasNext())
InputStream stream = iter.next();
processSheet(styles, strings, stream);
stream.close();
catch (InvalidFormatException e)
e.printStackTrace();
catch (SAXException e)
e.printStackTrace();
catch (OpenXML4JException e)
e.printStackTrace();
protected void processSheet(StylesTable styles, ReadOnlySharedStringsTable strings, InputStream sheetInputStream) throws IOException, SAXException
InputSource sheetSource = new InputSource(sheetInputStream);
SAXParserFactory saxFactory = SAXParserFactory.newInstance();
try
SAXParser saxParser = saxFactory.newSAXParser();
XMLReader sheetParser = saxParser.getXMLReader();
ContentHandler handler = new XSSFSheetXMLHandler(styles, strings, new SheetContentsHandler()
@Override
public void startRow(int rowNum)
@Override
public void endRow()
@Override
public void cell(String cellReference, String formattedValue)
@Override
public void headerFooter(String text, boolean isHeader, String tagName)
,
false//means result instead of formula
);
sheetParser.setContentHandler(handler);
sheetParser.parse(sheetSource);
catch (ParserConfigurationException e)
throw new RuntimeException("SAX parser appears to be broken - " + e.getMessage());
【讨论】:
感谢 O.C 正是我正在寻找的处理超过 250k 行的内容。完美的作品。 非常感谢上面的代码 sn-p。 Apache POI 应该在他们的文档中发布一个与上述示例一样的示例,以便更容易地宣传这些 API。 @O.C 非常感谢!!你能告诉如何使用上面的代码在excel中考虑空白单元格吗? 有没有办法使用基于迭代器/基于行的方法?我想用 hasNext() 和 next() 方法围绕它包装一个迭代器,以便调用者有更大的影响力。在这种基于事件的方法中,我无法控制进度,因为我必须获取所有事件,直到没有事件为止。 但这是一个 xlsx 解析器而不是 xls 解析器 :(【参考方案2】:你没有提到是否需要修改电子表格。
这可能很明显,但是如果您不需要修改电子表格,那么您不需要解析它并将其写回,您可以简单地从文件中读取字节,并写出字节,如你会用,比如图像,或任何其他二进制格式。
如果您确实需要在将电子表格发送给用户之前对其进行修改,那么据我所知,您可能需要采取不同的方法。
我所知道的用于在 Java 中读取 Excel 文件的每个库都会将整个电子表格读取到内存中,因此您必须为每个可能同时处理的电子表格提供 50MB 的可用内存。正如其他人指出的那样,这涉及调整 VM 可用的堆。
如果您需要同时处理大量电子表格,并且无法分配足够的内存,请考虑使用可以流式传输的格式,而不是一次性将所有电子表格读入内存。 CSV格式可以用Excel打开,过去我通过将content-type设置为application/vnd.ms-excel,将附件文件名设置为以“.xls”结尾的东西,但实际上返回的是CSV内容。我已经有几年没有尝试过了,所以 YMMV。
【讨论】:
【参考方案3】:在 bellwo 示例中,我将添加一个完整的代码,如何将完整的 excel 文件(对我来说是 60Mo)解析为对象列表,而不会出现“内存不足”的任何问题并且工作正常:
import java.util.ArrayList;
import java.util.List;
class DistinctByProperty
private static OPCPackage xlsxPackage = null;
private static PrintStream output= System.out;
private static List<MassUpdateMonitoringRow> resultMapping = new ArrayList<>();
public static void main(String[] args) throws IOException
File file = new File("C:\\Users\\aberguig032018\\Downloads\\your_excel.xlsx");
double bytes = file.length();
double kilobytes = (bytes / 1024);
double megabytes = (kilobytes / 1024);
System.out.println("Size "+megabytes);
parseExcel(file);
public static void parseExcel(File file) throws IOException
try
xlsxPackage = OPCPackage.open(file.getAbsolutePath(), PackageAccess.READ);
ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(xlsxPackage);
XSSFReader xssfReader = new XSSFReader(xlsxPackage);
StylesTable styles = xssfReader.getStylesTable();
XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) xssfReader.getSheetsData();
int index = 0;
while (iter.hasNext())
try (InputStream stream = iter.next())
String sheetName = iter.getSheetName();
output.println();
output.println(sheetName + " [index=" + index + "]:");
processSheet(styles, strings, new MappingFromXml(resultMapping), stream);
++index;
catch (InvalidFormatException e)
e.printStackTrace();
catch (OpenXML4JException e)
e.printStackTrace();
catch (SAXException e)
e.printStackTrace();
private static void processSheet(StylesTable styles, ReadOnlySharedStringsTable strings, MappingFromXml mappingFromXml, InputStream sheetInputStream) throws IOException, SAXException
DataFormatter formatter = new DataFormatter();
InputSource sheetSource = new InputSource(sheetInputStream);
try
XMLReader sheetParser = SAXHelper.newXMLReader();
ContentHandler handler = new XSSFSheetXMLHandler(
styles, null, strings, mappingFromXml, formatter, false);
sheetParser.setContentHandler(handler);
sheetParser.parse(sheetSource);
System.out.println("Size of Array "+resultMapping.size());
catch(ParserConfigurationException e)
throw new RuntimeException("SAX parser appears to be broken - " + e.getMessage());
你必须添加一个实现的类
SheetContentsHandler
import com.sun.org.apache.xpath.internal.operations.Bool;
import org.apache.poi.ss.util.CellAddress;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler.SheetContentsHandler;
import org.apache.poi.xssf.usermodel.XSSFComment;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
public class MappingFromXml implements SheetContentsHandler
private List<myObject> result = new ArrayList<>();
private myObject myObject = null;
private int lineNumber = 0;
/**
* Number of columns to read starting with leftmost
*/
private int minColumns = 25;
/**
* Destination for data
*/
private PrintStream output = System.out;
public MappingFromXml(List<myObject> list)
this.result = list;
@Override
public void startRow(int i)
output.println("iii " + i);
lineNumber = i;
myObject = new myObject();
@Override
public void endRow(int i)
output.println("jjj " + i);
result.add(myObject);
myObject = null;
@Override
public void cell(String cellReference, String formattedValue, XSSFComment comment)
int columnIndex = (new CellReference(cellReference)).getCol();
if(lineNumber > 0)
switch (columnIndex)
case 0: //Tech id
if (formattedValue != null && !formattedValue.isEmpty())
myObject.setId(Integer.parseInt(formattedValue));
break;
//TODO add other cell
@Override
public void headerFooter(String s, boolean b, String s1)
欲了解更多信息,请访问link
【讨论】:
【参考方案4】:我在解析xlsx文件时也遇到了同样的OOM问题……经过两天的努力,我终于发现下面的代码真的很完美;
此代码基于 sjxlsx。它读取 xlsx 并存储在 HSSF 表中。
[code=java]
// read the xlsx file
SimpleXLSXWorkbook = new SimpleXLSXWorkbook(new File("C:/test.xlsx"));
HSSFWorkbook hsfWorkbook = new HSSFWorkbook();
org.apache.poi.ss.usermodel.Sheet hsfSheet = hsfWorkbook.createSheet();
Sheet sheetToRead = workbook.getSheet(0, false);
SheetRowReader reader = sheetToRead.newReader();
Cell[] row;
int rowPos = 0;
while ((row = reader.readRow()) != null)
org.apache.poi.ss.usermodel.Row hfsRow = hsfSheet.createRow(rowPos);
int cellPos = 0;
for (Cell cell : row)
if(cell != null)
org.apache.poi.ss.usermodel.Cell hfsCell = hfsRow.createCell(cellPos);
hfsCell.setCellType(org.apache.poi.ss.usermodel.Cell.CELL_TYPE_STRING);
hfsCell.setCellValue(cell.getValue());
cellPos++;
rowPos++;
return hsfSheet;[/code]
【讨论】:
这个例子展示了如何写入一个excel文件,问题是我们如何在poi中写入一个excel文件。以上是关于通过 Apache POI 读取大型 Excel 文件 (xlsx) 时出错的主要内容,如果未能解决你的问题,请参考以下文章